blob: 6dd4886f30c149a7d14332bfe7de6d84ed3c3404 [file] [log] [blame]
mpagenkoc26d4c02021-05-06 14:27:57 +00001/*
Joey Armstronge8c091f2023-01-17 16:56:26 -05002 * Copyright 2020-2023 Open Networking Foundation (ONF) and the ONF Contributors
mpagenkoc26d4c02021-05-06 14:27:57 +00003 *
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
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000017//Package swupg provides the utilities for onu sw upgrade
18package swupg
mpagenkoc26d4c02021-05-06 14:27:57 +000019
20import (
21 "bufio"
22 "context"
23 "fmt"
24 "io"
25 "net/http"
26 "net/url"
27 "os"
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +030028 "path/filepath"
mpagenkoc26d4c02021-05-06 14:27:57 +000029 "sync"
30 "time"
31
khenaidoo7d3c5582021-08-11 18:09:44 -040032 "github.com/opencord/voltha-lib-go/v7/pkg/log"
mpagenkoc26d4c02021-05-06 14:27:57 +000033)
34
35const cDefaultLocalDir = "/tmp" //this is the default local dir to download to
36
mpagenkoc497ee32021-11-10 17:30:20 +000037// FileState defines the download state of the ONU software image
38type FileState uint32
mpagenkoc26d4c02021-05-06 14:27:57 +000039
40//nolint:varcheck, deadcode
41const (
mpagenkoc497ee32021-11-10 17:30:20 +000042 //CFileStateUnknown: the download state is not really known
43 CFileStateUnknown FileState = iota
44 //CFileStateDlStarted: the download to adapter has been started
45 CFileStateDlStarted
46 //CFileStateDlSucceeded: the download to adapter is successfully done (file exists and ready to use)
47 CFileStateDlSucceeded
48 //CFileStateDlFailed: the download to adapter has failed
49 CFileStateDlFailed
50 //CFileStateDlAborted: the download to adapter was aborted
51 CFileStateDlAborted
mpagenkoc26d4c02021-05-06 14:27:57 +000052)
53
54type downloadImageParams struct {
mpagenko39b703e2021-08-25 13:38:40 +000055 downloadImageName string
mpagenkoc497ee32021-11-10 17:30:20 +000056 downloadImageURL string
57 downloadImageState FileState
mpagenko39b703e2021-08-25 13:38:40 +000058 downloadImageLen int64
mpagenkoc497ee32021-11-10 17:30:20 +000059 downloadImageCRC uint32
mpagenko39b703e2021-08-25 13:38:40 +000060 downloadActive bool
61 downloadContextCancelFn context.CancelFunc
mpagenkoc26d4c02021-05-06 14:27:57 +000062}
63
64type requesterChannelMap map[chan<- bool]struct{} //using an empty structure map for easier (unique) element appending
65
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000066//FileDownloadManager structure holds information needed for downloading to and storing images within the adapter
67type FileDownloadManager struct {
mpagenkoc497ee32021-11-10 17:30:20 +000068 mutexFileState sync.RWMutex
mpagenkoc26d4c02021-05-06 14:27:57 +000069 mutexDownloadImageDsc sync.RWMutex
70 downloadImageDscSlice []downloadImageParams
71 dnldImgReadyWaiting map[string]requesterChannelMap
72 dlToAdapterTimeout time.Duration
73}
74
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000075//NewFileDownloadManager constructor returns a new instance of a FileDownloadManager
mpagenkoc26d4c02021-05-06 14:27:57 +000076//mib_db (as well as not inluded alarm_db not really used in this code? VERIFY!!)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000077func NewFileDownloadManager(ctx context.Context) *FileDownloadManager {
78 logger.Debug(ctx, "init-FileDownloadManager")
79 var localDnldMgr FileDownloadManager
mpagenkoc26d4c02021-05-06 14:27:57 +000080 localDnldMgr.downloadImageDscSlice = make([]downloadImageParams, 0)
81 localDnldMgr.dnldImgReadyWaiting = make(map[string]requesterChannelMap)
82 localDnldMgr.dlToAdapterTimeout = 10 * time.Second //default timeout, should be overwritten immediately after start
83 return &localDnldMgr
84}
85
86//SetDownloadTimeout configures the timeout used to supervice the download of the image to the adapter (assumed in seconds)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000087func (dm *FileDownloadManager) SetDownloadTimeout(ctx context.Context, aDlTimeout time.Duration) {
mpagenkoc26d4c02021-05-06 14:27:57 +000088 dm.mutexDownloadImageDsc.Lock()
89 defer dm.mutexDownloadImageDsc.Unlock()
90 logger.Debugw(ctx, "setting download timeout", log.Fields{"timeout": aDlTimeout})
91 dm.dlToAdapterTimeout = aDlTimeout
92}
93
94//GetDownloadTimeout delivers the timeout used to supervice the download of the image to the adapter (assumed in seconds)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000095func (dm *FileDownloadManager) GetDownloadTimeout(ctx context.Context) time.Duration {
mpagenkoc26d4c02021-05-06 14:27:57 +000096 dm.mutexDownloadImageDsc.RLock()
97 defer dm.mutexDownloadImageDsc.RUnlock()
98 return dm.dlToAdapterTimeout
99}
100
mpagenkoc497ee32021-11-10 17:30:20 +0000101//StartDownload returns FileState and error code from download request for the given file name and URL
102func (dm *FileDownloadManager) StartDownload(ctx context.Context, aImageName string, aURLCommand string) (FileState, error) {
mpagenkoc26d4c02021-05-06 14:27:57 +0000103 logger.Infow(ctx, "image download-to-adapter requested", log.Fields{
104 "image-name": aImageName, "url-command": aURLCommand})
mpagenkoc497ee32021-11-10 17:30:20 +0000105
106 // keep a semaphore over the complete method in order to avoid parallel entrance to this method
107 // otherwise a temporary file state 'Started' could be indicated allowing start of ONU upgrade handling
108 // even though the download-start to adapter may fail (e.g on wrong URL) (delivering inconsistent download results)
109 // so once called the download-start of the first call must have been completely checked before another execution
110 // could still be limited to the same imageName, but that should be no real gain
111 dm.mutexFileState.Lock()
112 defer dm.mutexFileState.Unlock()
113 dm.mutexDownloadImageDsc.Lock()
114 var fileState FileState
115 var exists bool
116 if fileState, exists = dm.imageExists(ctx, aImageName, aURLCommand); !exists {
117 loDownloadImageParams := downloadImageParams{
118 downloadImageName: aImageName, downloadImageURL: aURLCommand, downloadImageState: CFileStateDlStarted,
119 downloadImageLen: 0, downloadImageCRC: 0}
mpagenko38662d02021-08-11 09:45:19 +0000120 dm.downloadImageDscSlice = append(dm.downloadImageDscSlice, loDownloadImageParams)
121 dm.mutexDownloadImageDsc.Unlock()
mpagenkoc497ee32021-11-10 17:30:20 +0000122 //start downloading from server
123 var err error
124 if err = dm.downloadFile(ctx, aURLCommand, cDefaultLocalDir, aImageName); err == nil {
125 //indicate download started correctly, complete download may run in background
126 return CFileStateDlStarted, nil
127 }
128 //return the error result of the download-request
129 return CFileStateUnknown, err
mpagenko38662d02021-08-11 09:45:19 +0000130 }
mpagenkoc497ee32021-11-10 17:30:20 +0000131 dm.mutexDownloadImageDsc.Unlock()
132 if fileState == CFileStateUnknown {
133 //cannot simply remove the existing file here - might still be used for running upgrades on different devices!
134 // (has to be removed by operator - cancel API)
135 logger.Errorw(ctx, "image download requested for existing file with different URL",
136 log.Fields{"image-description": aImageName, "url": aURLCommand})
137 return fileState, fmt.Errorf("existing file is based on different URL, requested URL: %s", aURLCommand)
138 }
139 logger.Debugw(ctx, "image download already started or done", log.Fields{"image-description": aImageName})
140 return fileState, nil
mpagenkoc26d4c02021-05-06 14:27:57 +0000141}
142
143//GetImageBufferLen returns the length of the specified file in bytes (file size) - as detected after download
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000144func (dm *FileDownloadManager) GetImageBufferLen(ctx context.Context, aFileName string) (int64, error) {
mpagenkoc26d4c02021-05-06 14:27:57 +0000145 dm.mutexDownloadImageDsc.RLock()
146 defer dm.mutexDownloadImageDsc.RUnlock()
147 for _, dnldImgDsc := range dm.downloadImageDscSlice {
mpagenkoc497ee32021-11-10 17:30:20 +0000148 if dnldImgDsc.downloadImageName == aFileName && dnldImgDsc.downloadImageState == CFileStateDlSucceeded {
mpagenkoc26d4c02021-05-06 14:27:57 +0000149 //image found (by name) and fully downloaded
150 return dnldImgDsc.downloadImageLen, nil
151 }
152 }
153 return 0, fmt.Errorf("no downloaded image found: %s", aFileName)
154}
155
156//GetDownloadImageBuffer returns the content of the requested file as byte slice
mpagenkoc497ee32021-11-10 17:30:20 +0000157//precondition: it is assumed that a check is done immediately before if the file was downloaded to adapter correctly from caller
158// straightforward approach is here to e.g. immediately call and verify GetImageBufferLen() before this
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000159func (dm *FileDownloadManager) GetDownloadImageBuffer(ctx context.Context, aFileName string) ([]byte, error) {
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300160 file, err := os.Open(filepath.Clean(cDefaultLocalDir + "/" + aFileName))
mpagenkoc26d4c02021-05-06 14:27:57 +0000161 if err != nil {
162 return nil, err
163 }
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300164 defer func() {
165 err := file.Close()
166 if err != nil {
167 logger.Errorw(ctx, "failed to close file", log.Fields{"error": err})
168 }
169 }()
mpagenkoc26d4c02021-05-06 14:27:57 +0000170
171 stats, statsErr := file.Stat()
172 if statsErr != nil {
173 return nil, statsErr
174 }
175
176 var size int64 = stats.Size()
177 bytes := make([]byte, size)
178
179 buffer := bufio.NewReader(file)
180 _, err = buffer.Read(bytes)
181
182 return bytes, err
183}
184
185//RequestDownloadReady receives a channel that has to be used to inform the requester in case the concerned file is downloaded
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000186func (dm *FileDownloadManager) RequestDownloadReady(ctx context.Context, aFileName string, aWaitChannel chan<- bool) {
mpagenko5b5cb982021-08-18 16:38:51 +0000187 //mutexDownloadImageDsc must already be locked here to avoid an update of the dnldImgReadyWaiting map
188 // just after returning false on imageLocallyDownloaded() (not found) and immediate handling of the
189 // download success (within updateFileState())
190 // so updateFileState() can't interfere here just after imageLocallyDownloaded() before setting the requester map
191 dm.mutexDownloadImageDsc.Lock()
192 defer dm.mutexDownloadImageDsc.Unlock()
mpagenkoc26d4c02021-05-06 14:27:57 +0000193 if dm.imageLocallyDownloaded(ctx, aFileName) {
194 //image found (by name) and fully downloaded
195 logger.Debugw(ctx, "file ready - immediate response", log.Fields{"image-name": aFileName})
196 aWaitChannel <- true
197 return
198 }
199 //when we are here the image was not yet found or not fully downloaded -
200 // add the device specific channel to the list of waiting requesters
mpagenkoc26d4c02021-05-06 14:27:57 +0000201 if loRequesterChannelMap, ok := dm.dnldImgReadyWaiting[aFileName]; ok {
202 //entry for the file name already exists
203 if _, exists := loRequesterChannelMap[aWaitChannel]; !exists {
204 // requester channel does not yet exist for the image
205 loRequesterChannelMap[aWaitChannel] = struct{}{}
206 dm.dnldImgReadyWaiting[aFileName] = loRequesterChannelMap
207 logger.Debugw(ctx, "file not ready - adding new requester", log.Fields{
208 "image-name": aFileName, "number-of-requesters": len(dm.dnldImgReadyWaiting[aFileName])})
209 }
210 } else {
211 //entry for the file name does not even exist
212 addRequesterChannelMap := make(map[chan<- bool]struct{})
213 addRequesterChannelMap[aWaitChannel] = struct{}{}
214 dm.dnldImgReadyWaiting[aFileName] = addRequesterChannelMap
215 logger.Debugw(ctx, "file not ready - setting first requester", log.Fields{
216 "image-name": aFileName})
217 }
218}
219
220//RemoveReadyRequest removes the specified channel from the requester(channel) map for the given file name
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000221func (dm *FileDownloadManager) RemoveReadyRequest(ctx context.Context, aFileName string, aWaitChannel chan bool) {
mpagenkoc26d4c02021-05-06 14:27:57 +0000222 dm.mutexDownloadImageDsc.Lock()
223 defer dm.mutexDownloadImageDsc.Unlock()
224 for imageName, channelMap := range dm.dnldImgReadyWaiting {
225 if imageName == aFileName {
226 for channel := range channelMap {
227 if channel == aWaitChannel {
228 delete(dm.dnldImgReadyWaiting[imageName], channel)
229 logger.Debugw(ctx, "channel removed from the requester map", log.Fields{
230 "image-name": aFileName, "new number-of-requesters": len(dm.dnldImgReadyWaiting[aFileName])})
231 return //can leave directly
232 }
233 }
234 return //can leave directly
235 }
236 }
237}
238
239// FileDownloadManager private (unexported) methods -- start
240
mpagenkoc497ee32021-11-10 17:30:20 +0000241//imageExists returns current ImageState and if the requested image already exists within the adapter
242//precondition: at calling this method mutexDownloadImageDsc must already be at least RLocked by the caller
243func (dm *FileDownloadManager) imageExists(ctx context.Context, aImageName string, aURL string) (FileState, bool) {
244 logger.Debugw(ctx, "checking on existence of the image", log.Fields{"image-name": aImageName})
245 for _, dnldImgDsc := range dm.downloadImageDscSlice {
246 if dnldImgDsc.downloadImageName == aImageName {
247 //image found (by name)
248 if dnldImgDsc.downloadImageURL == aURL {
249 //image found (by name and URL)
250 return dnldImgDsc.downloadImageState, true
251 }
252 return CFileStateUnknown, true //use fileState to indicate URL mismatch for existing file
253 }
254 }
255 //image not found (by name)
256 return CFileStateUnknown, false
257}
258
mpagenkoc26d4c02021-05-06 14:27:57 +0000259//imageLocallyDownloaded returns true if the requested image already exists within the adapter
mpagenko5b5cb982021-08-18 16:38:51 +0000260// requires mutexDownloadImageDsc to be locked (at least RLocked)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000261func (dm *FileDownloadManager) imageLocallyDownloaded(ctx context.Context, aImageName string) bool {
mpagenkoc26d4c02021-05-06 14:27:57 +0000262 logger.Debugw(ctx, "checking if image is fully downloaded to adapter", log.Fields{"image-name": aImageName})
mpagenkoc26d4c02021-05-06 14:27:57 +0000263 for _, dnldImgDsc := range dm.downloadImageDscSlice {
264 if dnldImgDsc.downloadImageName == aImageName {
265 //image found (by name)
mpagenkoc497ee32021-11-10 17:30:20 +0000266 if dnldImgDsc.downloadImageState == CFileStateDlSucceeded {
mpagenkoc26d4c02021-05-06 14:27:57 +0000267 logger.Debugw(ctx, "image has been fully downloaded", log.Fields{"image-name": aImageName})
268 return true
269 }
270 logger.Debugw(ctx, "image not yet fully downloaded", log.Fields{"image-name": aImageName})
271 return false
272 }
273 }
274 //image not found (by name)
275 logger.Errorw(ctx, "image does not exist", log.Fields{"image-name": aImageName})
276 return false
277}
278
mpagenko39b703e2021-08-25 13:38:40 +0000279//updateDownloadCancel sets context cancel function to be used in case the download is to be aborted
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000280func (dm *FileDownloadManager) updateDownloadCancel(ctx context.Context,
mpagenko39b703e2021-08-25 13:38:40 +0000281 aImageName string, aCancelFn context.CancelFunc) {
282 dm.mutexDownloadImageDsc.Lock()
283 defer dm.mutexDownloadImageDsc.Unlock()
284 for imgKey, dnldImgDsc := range dm.downloadImageDscSlice {
285 if dnldImgDsc.downloadImageName == aImageName {
286 //image found (by name) - need to write changes on the original map
287 dm.downloadImageDscSlice[imgKey].downloadContextCancelFn = aCancelFn
288 dm.downloadImageDscSlice[imgKey].downloadActive = true
289 logger.Debugw(ctx, "downloadContextCancelFn set", log.Fields{
290 "image-name": aImageName})
291 return //can leave directly
292 }
293 }
294}
295
mpagenko5b5cb982021-08-18 16:38:51 +0000296//updateFileState sets the new active (downloaded) file state and informs possibly waiting requesters on this change
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000297func (dm *FileDownloadManager) updateFileState(ctx context.Context, aImageName string, aFileSize int64) {
mpagenko5b5cb982021-08-18 16:38:51 +0000298 dm.mutexDownloadImageDsc.Lock()
299 defer dm.mutexDownloadImageDsc.Unlock()
300 for imgKey, dnldImgDsc := range dm.downloadImageDscSlice {
301 if dnldImgDsc.downloadImageName == aImageName {
302 //image found (by name) - need to write changes on the original map
mpagenko39b703e2021-08-25 13:38:40 +0000303 dm.downloadImageDscSlice[imgKey].downloadActive = false
mpagenkoc497ee32021-11-10 17:30:20 +0000304 dm.downloadImageDscSlice[imgKey].downloadImageState = CFileStateDlSucceeded
mpagenko5b5cb982021-08-18 16:38:51 +0000305 dm.downloadImageDscSlice[imgKey].downloadImageLen = aFileSize
306 logger.Debugw(ctx, "imageState download succeeded", log.Fields{
307 "image-name": aImageName, "image-size": aFileSize})
308 //in case upgrade process(es) was/were waiting for the file, inform them
309 for imageName, channelMap := range dm.dnldImgReadyWaiting {
310 if imageName == aImageName {
311 for channel := range channelMap {
312 // use all found channels to inform possible requesters about the existence of the file
313 channel <- true
314 delete(dm.dnldImgReadyWaiting[imageName], channel) //requester served
315 }
316 return //can leave directly
317 }
318 }
319 return //can leave directly
320 }
321 }
322}
323
mpagenkoc26d4c02021-05-06 14:27:57 +0000324//downloadFile downloads the specified file from the given http location
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000325func (dm *FileDownloadManager) downloadFile(ctx context.Context, aURLCommand string, aFilePath string, aFileName string) error {
mpagenkoc26d4c02021-05-06 14:27:57 +0000326 // Get the data
327 logger.Infow(ctx, "downloading with URL", log.Fields{"url": aURLCommand, "localPath": aFilePath})
328 // verifying the complete URL by parsing it to its URL elements
329 urlBase, err1 := url.Parse(aURLCommand)
330 if err1 != nil {
331 logger.Errorw(ctx, "could not set base url command", log.Fields{"url": aURLCommand, "error": err1})
mpagenkoc497ee32021-11-10 17:30:20 +0000332 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000333 return fmt.Errorf("could not set base url command: %s, error: %s", aURLCommand, err1)
334 }
335 urlParams := url.Values{}
336 urlBase.RawQuery = urlParams.Encode()
337
338 //pre-check on file existence - assuming http location here
339 reqExist, errExist2 := http.NewRequest("HEAD", urlBase.String(), nil)
340 if errExist2 != nil {
341 logger.Errorw(ctx, "could not generate http head request", log.Fields{"url": urlBase.String(), "error": errExist2})
mpagenkoc497ee32021-11-10 17:30:20 +0000342 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000343 return fmt.Errorf("could not generate http head request: %s, error: %s", aURLCommand, errExist2)
344 }
345 ctxExist, cancelExist := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) //waiting for some fast answer
346 defer cancelExist()
347 _ = reqExist.WithContext(ctxExist)
348 respExist, errExist3 := http.DefaultClient.Do(reqExist)
349 if errExist3 != nil || respExist.StatusCode != http.StatusOK {
mpagenko39b703e2021-08-25 13:38:40 +0000350 if respExist == nil {
351 logger.Errorw(ctx, "http head from url error - no status, aborting", log.Fields{"url": urlBase.String(),
352 "error": errExist3})
mpagenkoc497ee32021-11-10 17:30:20 +0000353 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenko39b703e2021-08-25 13:38:40 +0000354 return fmt.Errorf("http head from url error - no status, aborting: %s, error: %s",
355 aURLCommand, errExist3)
356 }
mpagenkoc26d4c02021-05-06 14:27:57 +0000357 logger.Infow(ctx, "could not http head from url", log.Fields{"url": urlBase.String(),
358 "error": errExist3, "status": respExist.StatusCode})
359 //if head is not supported by server we cannot use this test and just try to continue
360 if respExist.StatusCode != http.StatusMethodNotAllowed {
361 logger.Errorw(ctx, "http head from url: file does not exist here, aborting", log.Fields{"url": urlBase.String(),
362 "error": errExist3, "status": respExist.StatusCode})
mpagenkoc497ee32021-11-10 17:30:20 +0000363 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000364 return fmt.Errorf("http head from url: file does not exist here, aborting: %s, error: %s, status: %d",
mpagenko39b703e2021-08-25 13:38:40 +0000365 aURLCommand, errExist3, respExist.StatusCode)
mpagenkoc26d4c02021-05-06 14:27:57 +0000366 }
367 }
368 defer func() {
369 deferredErr := respExist.Body.Close()
370 if deferredErr != nil {
371 logger.Errorw(ctx, "error at closing http head response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
372 }
373 }()
374
375 //trying to download - do it in background as it may take some time ...
376 go func() {
377 req, err2 := http.NewRequest("GET", urlBase.String(), nil)
378 if err2 != nil {
379 logger.Errorw(ctx, "could not generate http request", log.Fields{"url": urlBase.String(), "error": err2})
mpagenko38662d02021-08-11 09:45:19 +0000380 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000381 return
382 }
383 ctx, cancel := context.WithDeadline(ctx, time.Now().Add(dm.dlToAdapterTimeout)) //timeout as given from SetDownloadTimeout()
mpagenko39b703e2021-08-25 13:38:40 +0000384 dm.updateDownloadCancel(ctx, aFileName, cancel)
mpagenkoc26d4c02021-05-06 14:27:57 +0000385 defer cancel()
386 _ = req.WithContext(ctx)
387 resp, err3 := http.DefaultClient.Do(req)
mpagenko39b703e2021-08-25 13:38:40 +0000388 if err3 != nil || resp.StatusCode != http.StatusOK {
389 if resp == nil {
390 logger.Errorw(ctx, "http get error - no status, aborting", log.Fields{"url": urlBase.String(),
391 "error": err3})
392 } else {
393 logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(),
394 "error": err3, "status": resp.StatusCode})
395 }
mpagenko38662d02021-08-11 09:45:19 +0000396 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000397 return
398 }
399 defer func() {
400 deferredErr := resp.Body.Close()
401 if deferredErr != nil {
402 logger.Errorw(ctx, "error at closing http get response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
403 }
404 }()
405
406 // Create the file
407 aLocalPathName := aFilePath + "/" + aFileName
408 file, err := os.Create(aLocalPathName)
409 if err != nil {
410 logger.Errorw(ctx, "could not create local file", log.Fields{"path_file": aLocalPathName, "error": err})
mpagenko38662d02021-08-11 09:45:19 +0000411 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000412 return
413 }
414 defer func() {
415 deferredErr := file.Close()
416 if deferredErr != nil {
417 logger.Errorw(ctx, "error at closing new file", log.Fields{"path_file": aLocalPathName, "error": deferredErr})
418 }
419 }()
420
421 // Write the body to file
422 _, err = io.Copy(file, resp.Body)
423 if err != nil {
424 logger.Errorw(ctx, "could not copy file content", log.Fields{"url": urlBase.String(), "file": aLocalPathName, "error": err})
mpagenko38662d02021-08-11 09:45:19 +0000425 dm.removeImage(ctx, aFileName, true)
mpagenkoc26d4c02021-05-06 14:27:57 +0000426 return
427 }
428
429 fileStats, statsErr := file.Stat()
430 if err != nil {
431 logger.Errorw(ctx, "created file can't be accessed", log.Fields{"file": aLocalPathName, "stat-error": statsErr})
mpagenko5b5cb982021-08-18 16:38:51 +0000432 return
mpagenkoc26d4c02021-05-06 14:27:57 +0000433 }
434 fileSize := fileStats.Size()
435 logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPathName, "length": fileSize})
436
mpagenko5b5cb982021-08-18 16:38:51 +0000437 dm.updateFileState(ctx, aFileName, fileSize)
mpagenkoc26d4c02021-05-06 14:27:57 +0000438 //TODO:!!! further extension could be provided here, e.g. already computing and possibly comparing the CRC, vendor check
439 }()
440 return nil
441}
mpagenkoaa3afe92021-05-21 16:20:58 +0000442
mpagenko38662d02021-08-11 09:45:19 +0000443//removeImage deletes the given image according to the Image name from filesystem and downloadImageDscSlice
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000444func (dm *FileDownloadManager) removeImage(ctx context.Context, aImageName string, aDelFs bool) {
mpagenkoc497ee32021-11-10 17:30:20 +0000445 logger.Debugw(ctx, "remove the image from Adapter", log.Fields{"image-name": aImageName, "del-fs": aDelFs})
mpagenkoaa3afe92021-05-21 16:20:58 +0000446 dm.mutexDownloadImageDsc.RLock()
447 defer dm.mutexDownloadImageDsc.RUnlock()
448
449 tmpSlice := dm.downloadImageDscSlice[:0]
450 for _, dnldImgDsc := range dm.downloadImageDscSlice {
451 if dnldImgDsc.downloadImageName == aImageName {
mpagenko38662d02021-08-11 09:45:19 +0000452 //image found (by name)
mpagenkoaa3afe92021-05-21 16:20:58 +0000453 logger.Debugw(ctx, "removing image", log.Fields{"image-name": aImageName})
mpagenko38662d02021-08-11 09:45:19 +0000454 if aDelFs {
455 //remove the image from filesystem
456 aLocalPathName := cDefaultLocalDir + "/" + aImageName
457 if err := os.Remove(aLocalPathName); err != nil {
458 // might be a temporary situation, when the file was not yet (completely) written
459 logger.Debugw(ctx, "image not removed from filesystem", log.Fields{
460 "image-name": aImageName, "error": err})
461 }
mpagenkoaa3afe92021-05-21 16:20:58 +0000462 }
mpagenko38662d02021-08-11 09:45:19 +0000463 // and remove from the imageDsc slice by just not appending
mpagenkoaa3afe92021-05-21 16:20:58 +0000464 } else {
465 tmpSlice = append(tmpSlice, dnldImgDsc)
466 }
467 }
468 dm.downloadImageDscSlice = tmpSlice
469 //image not found (by name)
470}
mpagenko38662d02021-08-11 09:45:19 +0000471
mpagenko39b703e2021-08-25 13:38:40 +0000472//CancelDownload stops the download and clears all entires concerning this aimageName
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000473func (dm *FileDownloadManager) CancelDownload(ctx context.Context, aImageName string) {
mpagenko38662d02021-08-11 09:45:19 +0000474 // for the moment that would only support to wait for the download end and remove the image then
475 // further reactions while still downloading can be considered with some effort, but does it make sense (synchronous load here!)
mpagenko39b703e2021-08-25 13:38:40 +0000476 dm.mutexDownloadImageDsc.RLock()
477 for imgKey, dnldImgDsc := range dm.downloadImageDscSlice {
478 if dnldImgDsc.downloadImageName == aImageName {
479 //image found (by name) - need to to check on ongoing download
480 if dm.downloadImageDscSlice[imgKey].downloadActive {
481 //then cancel the download using the context cancel function
482 dm.downloadImageDscSlice[imgKey].downloadContextCancelFn()
483 }
484 //and remove possibly stored traces of this image
485 dm.mutexDownloadImageDsc.RUnlock()
486 go dm.removeImage(ctx, aImageName, true) //including the chance that nothing was yet written to FS, should not matter
487 return //can leave directly
488 }
489 }
490 dm.mutexDownloadImageDsc.RUnlock()
mpagenko38662d02021-08-11 09:45:19 +0000491}