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