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 |
| 49 | } |
| 50 | |
| 51 | //newAdapterDownloadManager constructor returns a new instance of a adapterDownloadManager |
| 52 | //mib_db (as well as not inluded alarm_db not really used in this code? VERIFY!!) |
| 53 | func newAdapterDownloadManager(ctx context.Context) *adapterDownloadManager { |
| 54 | logger.Debug(ctx, "init-adapterDownloadManager") |
| 55 | var localDnldMgr adapterDownloadManager |
| 56 | localDnldMgr.downloadImageDscSlice = make([]*voltha.ImageDownload, 0) |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 57 | return &localDnldMgr |
| 58 | } |
| 59 | |
| 60 | //imageExists returns true if the requested image already exists within the adapter |
| 61 | func (dm *adapterDownloadManager) imageExists(ctx context.Context, apImageDsc *voltha.ImageDownload) bool { |
| 62 | logger.Debugw(ctx, "checking on existence of the image", log.Fields{"image-name": (*apImageDsc).Name}) |
| 63 | dm.mutexDownloadImageDsc.RLock() |
| 64 | defer dm.mutexDownloadImageDsc.RUnlock() |
| 65 | |
| 66 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 67 | if (*pDnldImgDsc).Name == (*apImageDsc).Name { |
| 68 | //image found (by name) |
| 69 | return true |
| 70 | } |
| 71 | } |
| 72 | //image not found (by name) |
| 73 | return false |
| 74 | } |
| 75 | |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 76 | //imageLocallyDownloaded returns true if the requested image already exists within the adapter |
| 77 | func (dm *adapterDownloadManager) imageLocallyDownloaded(ctx context.Context, apImageDsc *voltha.ImageDownload) bool { |
| 78 | logger.Debugw(ctx, "checking if image is fully downloaded", log.Fields{"image-name": (*apImageDsc).Name}) |
| 79 | dm.mutexDownloadImageDsc.RLock() |
| 80 | defer dm.mutexDownloadImageDsc.RUnlock() |
| 81 | |
| 82 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 83 | if (*pDnldImgDsc).Name == (*apImageDsc).Name { |
| 84 | //image found (by name) |
| 85 | if (*pDnldImgDsc).DownloadState == voltha.ImageDownload_DOWNLOAD_SUCCEEDED { |
| 86 | logger.Debugw(ctx, "image has been fully downloaded", log.Fields{"image-name": (*apImageDsc).Name}) |
| 87 | return true |
| 88 | } |
| 89 | logger.Debugw(ctx, "image not yet fully downloaded", log.Fields{"image-name": (*apImageDsc).Name}) |
| 90 | return false |
| 91 | } |
| 92 | } |
| 93 | //image not found (by name) |
| 94 | logger.Errorw(ctx, "image does not exist", log.Fields{"image-name": (*apImageDsc).Name}) |
| 95 | return false |
| 96 | } |
| 97 | |
mpagenko | c8bba41 | 2021-01-15 15:38:44 +0000 | [diff] [blame] | 98 | //startDownload returns true if the download of the requested image could be started |
| 99 | func (dm *adapterDownloadManager) startDownload(ctx context.Context, apImageDsc *voltha.ImageDownload) error { |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 100 | if apImageDsc.LocalDir != "" { |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 101 | logger.Infow(ctx, "image download-to-adapter requested", log.Fields{ |
| 102 | "image-path": apImageDsc.LocalDir, "image-name": apImageDsc.Name}) |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 103 | newImageDscPos := len(dm.downloadImageDscSlice) |
| 104 | dm.downloadImageDscSlice = append(dm.downloadImageDscSlice, apImageDsc) |
| 105 | dm.downloadImageDscSlice[newImageDscPos].DownloadState = voltha.ImageDownload_DOWNLOAD_STARTED |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 106 | //try to download from http |
| 107 | urlName := apImageDsc.Url + "/" + apImageDsc.Name |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 108 | err := dm.downloadFile(ctx, urlName, apImageDsc.LocalDir, apImageDsc.Name) |
| 109 | if err != nil { |
| 110 | return (err) |
| 111 | } |
mpagenko | 15ff4a5 | 2021-03-02 10:09:20 +0000 | [diff] [blame] | 112 | //return success to comfort the core processing during integration |
| 113 | return nil |
| 114 | } |
| 115 | // we can use the missing local path temporary also to test some failure behavior (system reation on failure) |
| 116 | // with updated control API's or at some adequate time we could also set some defined fixed localPath internally |
| 117 | logger.Errorw(ctx, "could not start download: no valid local directory to write to", log.Fields{"image-name": (*apImageDsc).Name}) |
| 118 | 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] | 119 | } |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 120 | |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 121 | //downloadFile downloads the specified file from the given http location |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 122 | 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] | 123 | // Get the data |
| 124 | logger.Infow(ctx, "downloading from http", log.Fields{"url": aURLName, "localPath": aFilePath}) |
| 125 | // http command is already part of the aURLName argument |
| 126 | urlBase, err1 := url.Parse(aURLName) |
| 127 | if err1 != nil { |
| 128 | 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] | 129 | 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] | 130 | } |
| 131 | urlParams := url.Values{} |
| 132 | urlBase.RawQuery = urlParams.Encode() |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 133 | |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 134 | //pre-check on file existence |
| 135 | reqExist, errExist2 := http.NewRequest("HEAD", urlBase.String(), nil) |
| 136 | if errExist2 != nil { |
| 137 | logger.Errorw(ctx, "could not generate http head request", log.Fields{"url": urlBase.String(), "error": errExist2}) |
| 138 | return fmt.Errorf("could not generate http head request: %s, error: %s", aURLName, errExist2) |
| 139 | } |
| 140 | ctxExist, cancelExist := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) //waiting for some fast answer |
| 141 | defer cancelExist() |
| 142 | _ = reqExist.WithContext(ctxExist) |
| 143 | respExist, errExist3 := http.DefaultClient.Do(reqExist) |
| 144 | if errExist3 != nil || respExist.StatusCode != http.StatusOK { |
| 145 | logger.Infow(ctx, "could not http head from url", log.Fields{"url": urlBase.String(), |
| 146 | "error": errExist3, "status": respExist.StatusCode}) |
| 147 | //if head is not supported by server we cannot use this test and just try to continue |
| 148 | if respExist.StatusCode != http.StatusMethodNotAllowed { |
| 149 | logger.Errorw(ctx, "http head from url: file does not exist here, aborting", log.Fields{"url": urlBase.String(), |
| 150 | "error": errExist3, "status": respExist.StatusCode}) |
| 151 | return fmt.Errorf("http head from url: file does not exist here, aborting: %s, error: %s, status: %d", |
| 152 | aURLName, errExist2, respExist.StatusCode) |
| 153 | } |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 154 | } |
| 155 | defer func() { |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 156 | deferredErr := respExist.Body.Close() |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 157 | if deferredErr != nil { |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 158 | 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] | 159 | } |
| 160 | }() |
| 161 | |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 162 | //trying to download - do it in background as it may take some time ... |
| 163 | go func() { |
| 164 | req, err2 := http.NewRequest("GET", urlBase.String(), nil) |
| 165 | if err2 != nil { |
| 166 | logger.Errorw(ctx, "could not generate http request", log.Fields{"url": urlBase.String(), "error": err2}) |
| 167 | return |
| 168 | } |
| 169 | ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) //long timeout for remote server and big file |
| 170 | defer cancel() |
| 171 | _ = req.WithContext(ctx) |
| 172 | resp, err3 := http.DefaultClient.Do(req) |
| 173 | if err3 != nil || respExist.StatusCode != http.StatusOK { |
| 174 | logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(), |
| 175 | "error": err3, "status": respExist.StatusCode}) |
| 176 | return |
| 177 | } |
| 178 | defer func() { |
| 179 | deferredErr := resp.Body.Close() |
| 180 | if deferredErr != nil { |
| 181 | logger.Errorw(ctx, "error at closing http get response body", log.Fields{"url": urlBase.String(), "error": deferredErr}) |
| 182 | } |
| 183 | }() |
| 184 | |
| 185 | // Create the file |
| 186 | aLocalPathName := aFilePath + "/" + aFileName |
| 187 | file, err := os.Create(aLocalPathName) |
| 188 | if err != nil { |
| 189 | logger.Errorw(ctx, "could not create local file", log.Fields{"path_file": aLocalPathName, "error": err}) |
| 190 | return |
| 191 | } |
| 192 | defer func() { |
| 193 | deferredErr := file.Close() |
| 194 | if deferredErr != nil { |
| 195 | logger.Errorw(ctx, "error at closing new file", log.Fields{"path_file": aLocalPathName, "error": deferredErr}) |
| 196 | } |
| 197 | }() |
| 198 | |
| 199 | // Write the body to file |
| 200 | _, err = io.Copy(file, resp.Body) |
| 201 | if err != nil { |
| 202 | logger.Errorw(ctx, "could not copy file content", log.Fields{"url": urlBase.String(), "file": aLocalPathName, "error": err}) |
| 203 | return |
| 204 | } |
| 205 | |
| 206 | fileStats, statsErr := file.Stat() |
| 207 | if err != nil { |
| 208 | logger.Errorw(ctx, "created file can't be accessed", log.Fields{"file": aLocalPathName, "stat-error": statsErr}) |
| 209 | } |
| 210 | logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPathName, "length": fileStats.Size()}) |
| 211 | |
| 212 | for _, pDnldImgDsc := range dm.downloadImageDscSlice { |
| 213 | if (*pDnldImgDsc).Name == aFileName { |
| 214 | //image found (by name) |
| 215 | (*pDnldImgDsc).DownloadState = voltha.ImageDownload_DOWNLOAD_SUCCEEDED |
| 216 | return //can leave directly |
| 217 | } |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 218 | } |
| 219 | }() |
mpagenko | bf67a09 | 2021-03-17 09:52:28 +0000 | [diff] [blame] | 220 | return nil |
mpagenko | 02cf1b2 | 2021-03-12 17:30:30 +0000 | [diff] [blame] | 221 | } |
| 222 | |
mpagenko | 80622a5 | 2021-02-09 16:53:23 +0000 | [diff] [blame] | 223 | //getImageBufferLen returns the length of the specified file in bytes (file size) |
| 224 | func (dm *adapterDownloadManager) getImageBufferLen(ctx context.Context, aFileName string, |
| 225 | aLocalPath string) (int64, error) { |
| 226 | //maybe we can also use FileSize from dm.downloadImageDscSlice - future option? |
| 227 | |
| 228 | //nolint:gosec |
| 229 | file, err := os.Open(aLocalPath + "/" + aFileName) |
| 230 | if err != nil { |
| 231 | return 0, err |
| 232 | } |
| 233 | //nolint:errcheck |
| 234 | defer file.Close() |
| 235 | |
| 236 | stats, statsErr := file.Stat() |
| 237 | if statsErr != nil { |
| 238 | return 0, statsErr |
| 239 | } |
| 240 | |
| 241 | return stats.Size(), nil |
| 242 | } |
| 243 | |
| 244 | //getDownloadImageBuffer returns the content of the requested file as byte slice |
| 245 | func (dm *adapterDownloadManager) getDownloadImageBuffer(ctx context.Context, aFileName string, |
| 246 | aLocalPath string) ([]byte, error) { |
| 247 | //nolint:gosec |
| 248 | file, err := os.Open(aLocalPath + "/" + aFileName) |
| 249 | if err != nil { |
| 250 | return nil, err |
| 251 | } |
| 252 | //nolint:errcheck |
| 253 | defer file.Close() |
| 254 | |
| 255 | stats, statsErr := file.Stat() |
| 256 | if statsErr != nil { |
| 257 | return nil, statsErr |
| 258 | } |
| 259 | |
| 260 | var size int64 = stats.Size() |
| 261 | bytes := make([]byte, size) |
| 262 | |
| 263 | buffer := bufio.NewReader(file) |
| 264 | _, err = buffer.Read(bytes) |
| 265 | |
| 266 | return bytes, err |
| 267 | } |