mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020-present Open Networking Foundation |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //Package adaptercoreonu provides the utility for onu devices, flows and statistics |
| 18 | package adaptercoreonu |
| 19 | |
| 20 | import ( |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 21 | "bufio" |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 22 | "context" |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 23 | "errors" |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 24 | "fmt" |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 25 | "io" |
| 26 | "net/http" |
| 27 | "net/url" |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 28 | "os" |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 29 | "sync" |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 30 | "time" |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 31 | |
| 32 | "github.com/opencord/voltha-protos/v4/go/voltha" |
| 33 | |
| 34 | "github.com/opencord/voltha-lib-go/v4/pkg/log" |
| 35 | ) |
| 36 | |
| 37 | // ### downloadToAdapter related definitions #### |
| 38 | |
| 39 | //not yet defined to go with sca..., later also some configure options ?? |
| 40 | //const defaultDownloadTimeout = 60 // (?) Seconds |
| 41 | //const localImgPath = "/home/lcui/work/tmp" |
| 42 | |
| 43 | // ### downloadToAdapter - end #### |
| 44 | |
| 45 | //adapterDownloadManager structure holds information needed for downloading to and storing images within the adapter |
| 46 | type adapterDownloadManager struct { |
| 47 | mutexDownloadImageDsc sync.RWMutex |
| 48 | downloadImageDscSlice []*voltha.ImageDownload |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 49 | // maybe just for test purpose |
| 50 | arrayFileFragment [32]byte |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | //newAdapterDownloadManager constructor returns a new instance of a adapterDownloadManager |
| 54 | //mib_db (as well as not inluded alarm_db not really used in this code? VERIFY!!) |
| 55 | func newAdapterDownloadManager(ctx context.Context) *adapterDownloadManager { |
| 56 | logger.Debug(ctx, "init-adapterDownloadManager") |
| 57 | var localDnldMgr adapterDownloadManager |
| 58 | localDnldMgr.downloadImageDscSlice = make([]*voltha.ImageDownload, 0) |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 59 | return &localDnldMgr |
| 60 | } |
| 61 | |
| 62 | //imageExists returns true if the requested image already exists within the adapter |
| 63 | func (dm *adapterDownloadManager) imageExists(ctx context.Context, apImageDsc *voltha.ImageDownload) bool { |
| 64 | logger.Debugw(ctx, "checking on existence of the image", log.Fields{"image-name": (*apImageDsc).Name}) |
| 65 | dm.mutexDownloadImageDsc.RLock() |
| 66 | defer dm.mutexDownloadImageDsc.RUnlock() |
| 67 | |
| 68 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 69 | if (*pDnldImgDsc).Name == (*apImageDsc).Name { |
| 70 | //image found (by name) |
| 71 | return true |
| 72 | } |
| 73 | } |
| 74 | //image not found (by name) |
| 75 | return false |
| 76 | } |
| 77 | |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 78 | //imageLocallyDownloaded returns true if the requested image already exists within the adapter |
| 79 | func (dm *adapterDownloadManager) imageLocallyDownloaded(ctx context.Context, apImageDsc *voltha.ImageDownload) bool { |
| 80 | logger.Debugw(ctx, "checking if image is fully downloaded", log.Fields{"image-name": (*apImageDsc).Name}) |
| 81 | dm.mutexDownloadImageDsc.RLock() |
| 82 | defer dm.mutexDownloadImageDsc.RUnlock() |
| 83 | |
| 84 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 85 | if (*pDnldImgDsc).Name == (*apImageDsc).Name { |
| 86 | //image found (by name) |
| 87 | if (*pDnldImgDsc).DownloadState == voltha.ImageDownload_DOWNLOAD_SUCCEEDED { |
| 88 | logger.Debugw(ctx, "image has been fully downloaded", log.Fields{"image-name": (*apImageDsc).Name}) |
| 89 | return true |
| 90 | } |
| 91 | logger.Debugw(ctx, "image not yet fully downloaded", log.Fields{"image-name": (*apImageDsc).Name}) |
| 92 | return false |
| 93 | } |
| 94 | } |
| 95 | //image not found (by name) |
| 96 | logger.Errorw(ctx, "image does not exist", log.Fields{"image-name": (*apImageDsc).Name}) |
| 97 | return false |
| 98 | } |
| 99 | |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 100 | //startDownload returns true if the download of the requested image could be started |
| 101 | func (dm *adapterDownloadManager) startDownload(ctx context.Context, apImageDsc *voltha.ImageDownload) error { |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 102 | if apImageDsc.LocalDir != "" { |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 103 | logger.Infow(ctx, "image download-to-adapter requested", log.Fields{ |
| 104 | "image-path": apImageDsc.LocalDir, "image-name": apImageDsc.Name}) |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 105 | newImageDscPos := len(dm.downloadImageDscSlice) |
| 106 | dm.downloadImageDscSlice = append(dm.downloadImageDscSlice, apImageDsc) |
| 107 | dm.downloadImageDscSlice[newImageDscPos].DownloadState = voltha.ImageDownload_DOWNLOAD_STARTED |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 108 | if apImageDsc.LocalDir == "/intern" { |
| 109 | //just for initial 'internal' test verification |
| 110 | //just some basic test file simulation |
| 111 | dm.downloadImageDscSlice[newImageDscPos].LocalDir = "/tmp" |
| 112 | go dm.writeFileToLFS(ctx, "/tmp", apImageDsc.Name) |
| 113 | return nil |
| 114 | } else if apImageDsc.LocalDir == "/reboot" { |
| 115 | dm.downloadImageDscSlice[newImageDscPos].LocalDir = "/tmp" |
| 116 | } |
| 117 | //try to download from http |
| 118 | urlName := apImageDsc.Url + "/" + apImageDsc.Name |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 119 | err := dm.downloadFile(ctx, urlName, apImageDsc.LocalDir, apImageDsc.Name) |
| 120 | if err != nil { |
| 121 | return (err) |
| 122 | } |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 123 | //return success to comfort the core processing during integration |
| 124 | return nil |
| 125 | } |
| 126 | // we can use the missing local path temporary also to test some failure behavior (system reation on failure) |
| 127 | // with updated control API's or at some adequate time we could also set some defined fixed localPath internally |
| 128 | logger.Errorw(ctx, "could not start download: no valid local directory to write to", log.Fields{"image-name": (*apImageDsc).Name}) |
| 129 | return errors.New("could not start download: no valid local directory to write to") |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 130 | } |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 131 | |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 132 | //downloadFile downloads the specified file from the given http location |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 133 | func (dm *adapterDownloadManager) downloadFile(ctx context.Context, aURLName string, aFilePath string, aFileName string) error { |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 134 | // Get the data |
| 135 | logger.Infow(ctx, "downloading from http", log.Fields{"url": aURLName, "localPath": aFilePath}) |
| 136 | // http command is already part of the aURLName argument |
| 137 | urlBase, err1 := url.Parse(aURLName) |
| 138 | if err1 != nil { |
| 139 | logger.Errorw(ctx, "could not set base url command", log.Fields{"url": aURLName, "error": err1}) |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 140 | return fmt.Errorf("could not set base url command: %s, error: %s", aURLName, err1) |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 141 | } |
| 142 | urlParams := url.Values{} |
| 143 | urlBase.RawQuery = urlParams.Encode() |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 144 | |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 145 | //pre-check on file existence |
| 146 | reqExist, errExist2 := http.NewRequest("HEAD", urlBase.String(), nil) |
| 147 | if errExist2 != nil { |
| 148 | logger.Errorw(ctx, "could not generate http head request", log.Fields{"url": urlBase.String(), "error": errExist2}) |
| 149 | return fmt.Errorf("could not generate http head request: %s, error: %s", aURLName, errExist2) |
| 150 | } |
| 151 | ctxExist, cancelExist := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) //waiting for some fast answer |
| 152 | defer cancelExist() |
| 153 | _ = reqExist.WithContext(ctxExist) |
| 154 | respExist, errExist3 := http.DefaultClient.Do(reqExist) |
| 155 | if errExist3 != nil || respExist.StatusCode != http.StatusOK { |
| 156 | logger.Infow(ctx, "could not http head from url", log.Fields{"url": urlBase.String(), |
| 157 | "error": errExist3, "status": respExist.StatusCode}) |
| 158 | //if head is not supported by server we cannot use this test and just try to continue |
| 159 | if respExist.StatusCode != http.StatusMethodNotAllowed { |
| 160 | logger.Errorw(ctx, "http head from url: file does not exist here, aborting", log.Fields{"url": urlBase.String(), |
| 161 | "error": errExist3, "status": respExist.StatusCode}) |
| 162 | return fmt.Errorf("http head from url: file does not exist here, aborting: %s, error: %s, status: %d", |
| 163 | aURLName, errExist2, respExist.StatusCode) |
| 164 | } |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 165 | } |
| 166 | defer func() { |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 167 | deferredErr := respExist.Body.Close() |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 168 | if deferredErr != nil { |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 169 | logger.Errorw(ctx, "error at closing http head response body", log.Fields{"url": urlBase.String(), "error": deferredErr}) |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 170 | } |
| 171 | }() |
| 172 | |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 173 | //trying to download - do it in background as it may take some time ... |
| 174 | go func() { |
| 175 | req, err2 := http.NewRequest("GET", urlBase.String(), nil) |
| 176 | if err2 != nil { |
| 177 | logger.Errorw(ctx, "could not generate http request", log.Fields{"url": urlBase.String(), "error": err2}) |
| 178 | return |
| 179 | } |
| 180 | ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) //long timeout for remote server and big file |
| 181 | defer cancel() |
| 182 | _ = req.WithContext(ctx) |
| 183 | resp, err3 := http.DefaultClient.Do(req) |
| 184 | if err3 != nil || respExist.StatusCode != http.StatusOK { |
| 185 | logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(), |
| 186 | "error": err3, "status": respExist.StatusCode}) |
| 187 | return |
| 188 | } |
| 189 | defer func() { |
| 190 | deferredErr := resp.Body.Close() |
| 191 | if deferredErr != nil { |
| 192 | logger.Errorw(ctx, "error at closing http get response body", log.Fields{"url": urlBase.String(), "error": deferredErr}) |
| 193 | } |
| 194 | }() |
| 195 | |
| 196 | // Create the file |
| 197 | aLocalPathName := aFilePath + "/" + aFileName |
| 198 | file, err := os.Create(aLocalPathName) |
| 199 | if err != nil { |
| 200 | logger.Errorw(ctx, "could not create local file", log.Fields{"path_file": aLocalPathName, "error": err}) |
| 201 | return |
| 202 | } |
| 203 | defer func() { |
| 204 | deferredErr := file.Close() |
| 205 | if deferredErr != nil { |
| 206 | logger.Errorw(ctx, "error at closing new file", log.Fields{"path_file": aLocalPathName, "error": deferredErr}) |
| 207 | } |
| 208 | }() |
| 209 | |
| 210 | // Write the body to file |
| 211 | _, err = io.Copy(file, resp.Body) |
| 212 | if err != nil { |
| 213 | logger.Errorw(ctx, "could not copy file content", log.Fields{"url": urlBase.String(), "file": aLocalPathName, "error": err}) |
| 214 | return |
| 215 | } |
| 216 | |
| 217 | fileStats, statsErr := file.Stat() |
| 218 | if err != nil { |
| 219 | logger.Errorw(ctx, "created file can't be accessed", log.Fields{"file": aLocalPathName, "stat-error": statsErr}) |
| 220 | } |
| 221 | logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPathName, "length": fileStats.Size()}) |
| 222 | |
| 223 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 224 | if (*pDnldImgDsc).Name == aFileName { |
| 225 | //image found (by name) |
| 226 | (*pDnldImgDsc).DownloadState = voltha.ImageDownload_DOWNLOAD_SUCCEEDED |
| 227 | return //can leave directly |
| 228 | } |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 229 | } |
| 230 | }() |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame^] | 231 | return nil |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 232 | } |
| 233 | |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 234 | //writeFileToLFS writes the downloaded file to the local file system |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 235 | // this is just an internal test function and can be removed if other download capabilities exist |
| 236 | func (dm *adapterDownloadManager) writeFileToLFS(ctx context.Context, aLocalPath string, aFileName string) { |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 237 | // by now just a simulation to write a file with predefined 'variable' content |
| 238 | totalFileLength := 0 |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 239 | logger.Debugw(ctx, "writing fixed size simulation file locally", log.Fields{ |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 240 | "image-name": aFileName, "image-path": aLocalPath}) |
| 241 | file, err := os.Create(aLocalPath + "/" + aFileName) |
| 242 | if err == nil { |
| 243 | // write 32KB test file |
| 244 | for totalFileLength < 32*1024 { |
| 245 | if written, wrErr := file.Write(dm.getIncrementalSliceContent(ctx)); wrErr == nil { |
| 246 | totalFileLength += written |
| 247 | } else { |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 248 | logger.Errorw(ctx, "could not write to file", log.Fields{"create-error": wrErr}) |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 249 | break //stop writing |
| 250 | } |
| 251 | } |
| 252 | } else { |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 253 | logger.Errorw(ctx, "could not create file", log.Fields{"create-error": err}) |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 254 | } |
| 255 | |
| 256 | fileStats, statsErr := file.Stat() |
| 257 | if err != nil { |
| 258 | logger.Errorw(ctx, "created file can't be accessed", log.Fields{"stat-error": statsErr}) |
| 259 | } |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 260 | logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPath + "/" + aFileName, "length": fileStats.Size()}) |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 261 | |
| 262 | defer func() { |
| 263 | deferredErr := file.Close() |
| 264 | if deferredErr != nil { |
| 265 | logger.Errorw(ctx, "error at closing test file", log.Fields{"file": aLocalPath + "/" + aFileName, "error": deferredErr}) |
| 266 | } |
| 267 | }() |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 268 | |
| 269 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 270 | if (*pDnldImgDsc).Name == aFileName { |
| 271 | //image found (by name) |
| 272 | (*pDnldImgDsc).DownloadState = voltha.ImageDownload_DOWNLOAD_SUCCEEDED |
| 273 | return //can leave directly |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | //getImageBufferLen returns the length of the specified file in bytes (file size) |
| 279 | func (dm *adapterDownloadManager) getImageBufferLen(ctx context.Context, aFileName string, |
| 280 | aLocalPath string) (int64, error) { |
| 281 | //maybe we can also use FileSize from dm.downloadImageDscSlice - future option? |
| 282 | |
| 283 | //nolint:gosec |
| 284 | file, err := os.Open(aLocalPath + "/" + aFileName) |
| 285 | if err != nil { |
| 286 | return 0, err |
| 287 | } |
| 288 | //nolint:errcheck |
| 289 | defer file.Close() |
| 290 | |
| 291 | stats, statsErr := file.Stat() |
| 292 | if statsErr != nil { |
| 293 | return 0, statsErr |
| 294 | } |
| 295 | |
| 296 | return stats.Size(), nil |
| 297 | } |
| 298 | |
| 299 | //getDownloadImageBuffer returns the content of the requested file as byte slice |
| 300 | func (dm *adapterDownloadManager) getDownloadImageBuffer(ctx context.Context, aFileName string, |
| 301 | aLocalPath string) ([]byte, error) { |
| 302 | //nolint:gosec |
| 303 | file, err := os.Open(aLocalPath + "/" + aFileName) |
| 304 | if err != nil { |
| 305 | return nil, err |
| 306 | } |
| 307 | //nolint:errcheck |
| 308 | defer file.Close() |
| 309 | |
| 310 | stats, statsErr := file.Stat() |
| 311 | if statsErr != nil { |
| 312 | return nil, statsErr |
| 313 | } |
| 314 | |
| 315 | var size int64 = stats.Size() |
| 316 | bytes := make([]byte, size) |
| 317 | |
| 318 | buffer := bufio.NewReader(file) |
| 319 | _, err = buffer.Read(bytes) |
| 320 | |
| 321 | return bytes, err |
| 322 | } |
| 323 | |
| 324 | //getIncrementalSliceContent returns a byte slice of incremented bytes of internal array (used for file emulation) |
| 325 | // (used for file emulation) |
| 326 | func (dm *adapterDownloadManager) getIncrementalSliceContent(ctx context.Context) []byte { |
| 327 | lastValue := dm.arrayFileFragment[len(dm.arrayFileFragment)-1] |
| 328 | for index := range dm.arrayFileFragment { |
| 329 | lastValue++ |
| 330 | dm.arrayFileFragment[index] = lastValue |
| 331 | } |
| 332 | return dm.arrayFileFragment[:] |
| 333 | } |