blob: 05ffcf9d7b059450b8b5539c25368631294765fe [file] [log] [blame]
mpagenkoc26d4c02021-05-06 14:27:57 +00001/*
Joey Armstrong89c812c2024-01-12 19:00:20 -05002 * Copyright 2020-2024 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
Joey Armstrong89c812c2024-01-12 19:00:20 -050017// Package swupg provides the utilities for onu sw upgrade
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000018package 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
Joey Armstrong89c812c2024-01-12 19:00:20 -050066// FileDownloadManager structure holds information needed for downloading to and storing images within the adapter
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000067type 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
Joey Armstrong89c812c2024-01-12 19:00:20 -050075// NewFileDownloadManager constructor returns a new instance of a FileDownloadManager
76// 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
Joey Armstrong89c812c2024-01-12 19:00:20 -050086// 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
Joey Armstrong89c812c2024-01-12 19:00:20 -050094// 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
Joey Armstrong89c812c2024-01-12 19:00:20 -0500101// StartDownload returns FileState and error code from download request for the given file name and URL
mpagenkoc497ee32021-11-10 17:30:20 +0000102func (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
Joey Armstrong89c812c2024-01-12 19:00:20 -0500143// 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
Joey Armstrong89c812c2024-01-12 19:00:20 -0500156// GetDownloadImageBuffer returns the content of the requested file as byte slice
157// precondition: it is assumed that a check is done immediately before if the file was downloaded to adapter correctly from caller
158//
159// straightforward approach is here to e.g. immediately call and verify GetImageBufferLen() before this
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000160func (dm *FileDownloadManager) GetDownloadImageBuffer(ctx context.Context, aFileName string) ([]byte, error) {
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300161 file, err := os.Open(filepath.Clean(cDefaultLocalDir + "/" + aFileName))
mpagenkoc26d4c02021-05-06 14:27:57 +0000162 if err != nil {
163 return nil, err
164 }
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300165 defer func() {
166 err := file.Close()
167 if err != nil {
168 logger.Errorw(ctx, "failed to close file", log.Fields{"error": err})
169 }
170 }()
mpagenkoc26d4c02021-05-06 14:27:57 +0000171
172 stats, statsErr := file.Stat()
173 if statsErr != nil {
174 return nil, statsErr
175 }
176
177 var size int64 = stats.Size()
178 bytes := make([]byte, size)
179
180 buffer := bufio.NewReader(file)
181 _, err = buffer.Read(bytes)
182
183 return bytes, err
184}
185
Joey Armstrong89c812c2024-01-12 19:00:20 -0500186// 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 +0000187func (dm *FileDownloadManager) RequestDownloadReady(ctx context.Context, aFileName string, aWaitChannel chan<- bool) {
mpagenko5b5cb982021-08-18 16:38:51 +0000188 //mutexDownloadImageDsc must already be locked here to avoid an update of the dnldImgReadyWaiting map
189 // just after returning false on imageLocallyDownloaded() (not found) and immediate handling of the
190 // download success (within updateFileState())
191 // so updateFileState() can't interfere here just after imageLocallyDownloaded() before setting the requester map
192 dm.mutexDownloadImageDsc.Lock()
193 defer dm.mutexDownloadImageDsc.Unlock()
mpagenkoc26d4c02021-05-06 14:27:57 +0000194 if dm.imageLocallyDownloaded(ctx, aFileName) {
195 //image found (by name) and fully downloaded
196 logger.Debugw(ctx, "file ready - immediate response", log.Fields{"image-name": aFileName})
197 aWaitChannel <- true
198 return
199 }
200 //when we are here the image was not yet found or not fully downloaded -
201 // add the device specific channel to the list of waiting requesters
mpagenkoc26d4c02021-05-06 14:27:57 +0000202 if loRequesterChannelMap, ok := dm.dnldImgReadyWaiting[aFileName]; ok {
203 //entry for the file name already exists
204 if _, exists := loRequesterChannelMap[aWaitChannel]; !exists {
205 // requester channel does not yet exist for the image
206 loRequesterChannelMap[aWaitChannel] = struct{}{}
207 dm.dnldImgReadyWaiting[aFileName] = loRequesterChannelMap
208 logger.Debugw(ctx, "file not ready - adding new requester", log.Fields{
209 "image-name": aFileName, "number-of-requesters": len(dm.dnldImgReadyWaiting[aFileName])})
210 }
211 } else {
212 //entry for the file name does not even exist
213 addRequesterChannelMap := make(map[chan<- bool]struct{})
214 addRequesterChannelMap[aWaitChannel] = struct{}{}
215 dm.dnldImgReadyWaiting[aFileName] = addRequesterChannelMap
216 logger.Debugw(ctx, "file not ready - setting first requester", log.Fields{
217 "image-name": aFileName})
218 }
219}
220
Joey Armstrong89c812c2024-01-12 19:00:20 -0500221// RemoveReadyRequest removes the specified channel from the requester(channel) map for the given file name
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000222func (dm *FileDownloadManager) RemoveReadyRequest(ctx context.Context, aFileName string, aWaitChannel chan bool) {
mpagenkoc26d4c02021-05-06 14:27:57 +0000223 dm.mutexDownloadImageDsc.Lock()
224 defer dm.mutexDownloadImageDsc.Unlock()
225 for imageName, channelMap := range dm.dnldImgReadyWaiting {
226 if imageName == aFileName {
227 for channel := range channelMap {
228 if channel == aWaitChannel {
229 delete(dm.dnldImgReadyWaiting[imageName], channel)
230 logger.Debugw(ctx, "channel removed from the requester map", log.Fields{
231 "image-name": aFileName, "new number-of-requesters": len(dm.dnldImgReadyWaiting[aFileName])})
232 return //can leave directly
233 }
234 }
235 return //can leave directly
236 }
237 }
238}
239
240// FileDownloadManager private (unexported) methods -- start
241
Joey Armstrong89c812c2024-01-12 19:00:20 -0500242// imageExists returns current ImageState and if the requested image already exists within the adapter
243// precondition: at calling this method mutexDownloadImageDsc must already be at least RLocked by the caller
mpagenkoc497ee32021-11-10 17:30:20 +0000244func (dm *FileDownloadManager) imageExists(ctx context.Context, aImageName string, aURL string) (FileState, bool) {
245 logger.Debugw(ctx, "checking on existence of the image", log.Fields{"image-name": aImageName})
246 for _, dnldImgDsc := range dm.downloadImageDscSlice {
247 if dnldImgDsc.downloadImageName == aImageName {
248 //image found (by name)
249 if dnldImgDsc.downloadImageURL == aURL {
250 //image found (by name and URL)
251 return dnldImgDsc.downloadImageState, true
252 }
253 return CFileStateUnknown, true //use fileState to indicate URL mismatch for existing file
254 }
255 }
256 //image not found (by name)
257 return CFileStateUnknown, false
258}
259
Joey Armstrong89c812c2024-01-12 19:00:20 -0500260// imageLocallyDownloaded returns true if the requested image already exists within the adapter
261//
262// requires mutexDownloadImageDsc to be locked (at least RLocked)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000263func (dm *FileDownloadManager) imageLocallyDownloaded(ctx context.Context, aImageName string) bool {
mpagenkoc26d4c02021-05-06 14:27:57 +0000264 logger.Debugw(ctx, "checking if image is fully downloaded to adapter", log.Fields{"image-name": aImageName})
mpagenkoc26d4c02021-05-06 14:27:57 +0000265 for _, dnldImgDsc := range dm.downloadImageDscSlice {
266 if dnldImgDsc.downloadImageName == aImageName {
267 //image found (by name)
mpagenkoc497ee32021-11-10 17:30:20 +0000268 if dnldImgDsc.downloadImageState == CFileStateDlSucceeded {
mpagenkoc26d4c02021-05-06 14:27:57 +0000269 logger.Debugw(ctx, "image has been fully downloaded", log.Fields{"image-name": aImageName})
270 return true
271 }
272 logger.Debugw(ctx, "image not yet fully downloaded", log.Fields{"image-name": aImageName})
273 return false
274 }
275 }
276 //image not found (by name)
277 logger.Errorw(ctx, "image does not exist", log.Fields{"image-name": aImageName})
278 return false
279}
280
Joey Armstrong89c812c2024-01-12 19:00:20 -0500281// updateDownloadCancel sets context cancel function to be used in case the download is to be aborted
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000282func (dm *FileDownloadManager) updateDownloadCancel(ctx context.Context,
mpagenko39b703e2021-08-25 13:38:40 +0000283 aImageName string, aCancelFn context.CancelFunc) {
284 dm.mutexDownloadImageDsc.Lock()
285 defer dm.mutexDownloadImageDsc.Unlock()
286 for imgKey, dnldImgDsc := range dm.downloadImageDscSlice {
287 if dnldImgDsc.downloadImageName == aImageName {
288 //image found (by name) - need to write changes on the original map
289 dm.downloadImageDscSlice[imgKey].downloadContextCancelFn = aCancelFn
290 dm.downloadImageDscSlice[imgKey].downloadActive = true
291 logger.Debugw(ctx, "downloadContextCancelFn set", log.Fields{
292 "image-name": aImageName})
293 return //can leave directly
294 }
295 }
296}
297
Joey Armstrong89c812c2024-01-12 19:00:20 -0500298// updateFileState sets the new active (downloaded) file state and informs possibly waiting requesters on this change
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000299func (dm *FileDownloadManager) updateFileState(ctx context.Context, aImageName string, aFileSize int64) {
mpagenko5b5cb982021-08-18 16:38:51 +0000300 dm.mutexDownloadImageDsc.Lock()
301 defer dm.mutexDownloadImageDsc.Unlock()
302 for imgKey, dnldImgDsc := range dm.downloadImageDscSlice {
303 if dnldImgDsc.downloadImageName == aImageName {
304 //image found (by name) - need to write changes on the original map
mpagenko39b703e2021-08-25 13:38:40 +0000305 dm.downloadImageDscSlice[imgKey].downloadActive = false
mpagenkoc497ee32021-11-10 17:30:20 +0000306 dm.downloadImageDscSlice[imgKey].downloadImageState = CFileStateDlSucceeded
mpagenko5b5cb982021-08-18 16:38:51 +0000307 dm.downloadImageDscSlice[imgKey].downloadImageLen = aFileSize
308 logger.Debugw(ctx, "imageState download succeeded", log.Fields{
309 "image-name": aImageName, "image-size": aFileSize})
310 //in case upgrade process(es) was/were waiting for the file, inform them
311 for imageName, channelMap := range dm.dnldImgReadyWaiting {
312 if imageName == aImageName {
313 for channel := range channelMap {
314 // use all found channels to inform possible requesters about the existence of the file
315 channel <- true
316 delete(dm.dnldImgReadyWaiting[imageName], channel) //requester served
317 }
318 return //can leave directly
319 }
320 }
321 return //can leave directly
322 }
323 }
324}
325
Joey Armstrong89c812c2024-01-12 19:00:20 -0500326// downloadFile downloads the specified file from the given http location
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000327func (dm *FileDownloadManager) downloadFile(ctx context.Context, aURLCommand string, aFilePath string, aFileName string) error {
mpagenkoc26d4c02021-05-06 14:27:57 +0000328 // Get the data
329 logger.Infow(ctx, "downloading with URL", log.Fields{"url": aURLCommand, "localPath": aFilePath})
330 // verifying the complete URL by parsing it to its URL elements
331 urlBase, err1 := url.Parse(aURLCommand)
332 if err1 != nil {
333 logger.Errorw(ctx, "could not set base url command", log.Fields{"url": aURLCommand, "error": err1})
mpagenkoc497ee32021-11-10 17:30:20 +0000334 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000335 return fmt.Errorf("could not set base url command: %s, error: %s", aURLCommand, err1)
336 }
337 urlParams := url.Values{}
338 urlBase.RawQuery = urlParams.Encode()
339
340 //pre-check on file existence - assuming http location here
341 reqExist, errExist2 := http.NewRequest("HEAD", urlBase.String(), nil)
342 if errExist2 != nil {
343 logger.Errorw(ctx, "could not generate http head request", log.Fields{"url": urlBase.String(), "error": errExist2})
mpagenkoc497ee32021-11-10 17:30:20 +0000344 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000345 return fmt.Errorf("could not generate http head request: %s, error: %s", aURLCommand, errExist2)
346 }
347 ctxExist, cancelExist := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) //waiting for some fast answer
348 defer cancelExist()
349 _ = reqExist.WithContext(ctxExist)
350 respExist, errExist3 := http.DefaultClient.Do(reqExist)
351 if errExist3 != nil || respExist.StatusCode != http.StatusOK {
mpagenko39b703e2021-08-25 13:38:40 +0000352 if respExist == nil {
353 logger.Errorw(ctx, "http head from url error - no status, aborting", log.Fields{"url": urlBase.String(),
354 "error": errExist3})
mpagenkoc497ee32021-11-10 17:30:20 +0000355 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenko39b703e2021-08-25 13:38:40 +0000356 return fmt.Errorf("http head from url error - no status, aborting: %s, error: %s",
357 aURLCommand, errExist3)
358 }
mpagenkoc26d4c02021-05-06 14:27:57 +0000359 logger.Infow(ctx, "could not http head from url", log.Fields{"url": urlBase.String(),
360 "error": errExist3, "status": respExist.StatusCode})
361 //if head is not supported by server we cannot use this test and just try to continue
362 if respExist.StatusCode != http.StatusMethodNotAllowed {
363 logger.Errorw(ctx, "http head from url: file does not exist here, aborting", log.Fields{"url": urlBase.String(),
364 "error": errExist3, "status": respExist.StatusCode})
mpagenkoc497ee32021-11-10 17:30:20 +0000365 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000366 return fmt.Errorf("http head from url: file does not exist here, aborting: %s, error: %s, status: %d",
mpagenko39b703e2021-08-25 13:38:40 +0000367 aURLCommand, errExist3, respExist.StatusCode)
mpagenkoc26d4c02021-05-06 14:27:57 +0000368 }
369 }
370 defer func() {
371 deferredErr := respExist.Body.Close()
372 if deferredErr != nil {
373 logger.Errorw(ctx, "error at closing http head response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
374 }
375 }()
376
377 //trying to download - do it in background as it may take some time ...
378 go func() {
379 req, err2 := http.NewRequest("GET", urlBase.String(), nil)
380 if err2 != nil {
381 logger.Errorw(ctx, "could not generate http request", log.Fields{"url": urlBase.String(), "error": err2})
mpagenko38662d02021-08-11 09:45:19 +0000382 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000383 return
384 }
385 ctx, cancel := context.WithDeadline(ctx, time.Now().Add(dm.dlToAdapterTimeout)) //timeout as given from SetDownloadTimeout()
mpagenko39b703e2021-08-25 13:38:40 +0000386 dm.updateDownloadCancel(ctx, aFileName, cancel)
mpagenkoc26d4c02021-05-06 14:27:57 +0000387 defer cancel()
388 _ = req.WithContext(ctx)
389 resp, err3 := http.DefaultClient.Do(req)
mpagenko39b703e2021-08-25 13:38:40 +0000390 if err3 != nil || resp.StatusCode != http.StatusOK {
391 if resp == nil {
392 logger.Errorw(ctx, "http get error - no status, aborting", log.Fields{"url": urlBase.String(),
393 "error": err3})
394 } else {
395 logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(),
396 "error": err3, "status": resp.StatusCode})
397 }
mpagenko38662d02021-08-11 09:45:19 +0000398 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000399 return
400 }
401 defer func() {
402 deferredErr := resp.Body.Close()
403 if deferredErr != nil {
404 logger.Errorw(ctx, "error at closing http get response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
405 }
406 }()
407
408 // Create the file
409 aLocalPathName := aFilePath + "/" + aFileName
410 file, err := os.Create(aLocalPathName)
411 if err != nil {
412 logger.Errorw(ctx, "could not create local file", log.Fields{"path_file": aLocalPathName, "error": err})
mpagenko38662d02021-08-11 09:45:19 +0000413 dm.removeImage(ctx, aFileName, false) //wo FileSystem access
mpagenkoc26d4c02021-05-06 14:27:57 +0000414 return
415 }
416 defer func() {
417 deferredErr := file.Close()
418 if deferredErr != nil {
419 logger.Errorw(ctx, "error at closing new file", log.Fields{"path_file": aLocalPathName, "error": deferredErr})
420 }
421 }()
422
423 // Write the body to file
424 _, err = io.Copy(file, resp.Body)
425 if err != nil {
426 logger.Errorw(ctx, "could not copy file content", log.Fields{"url": urlBase.String(), "file": aLocalPathName, "error": err})
mpagenko38662d02021-08-11 09:45:19 +0000427 dm.removeImage(ctx, aFileName, true)
mpagenkoc26d4c02021-05-06 14:27:57 +0000428 return
429 }
430
431 fileStats, statsErr := file.Stat()
432 if err != nil {
433 logger.Errorw(ctx, "created file can't be accessed", log.Fields{"file": aLocalPathName, "stat-error": statsErr})
mpagenko5b5cb982021-08-18 16:38:51 +0000434 return
mpagenkoc26d4c02021-05-06 14:27:57 +0000435 }
436 fileSize := fileStats.Size()
437 logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPathName, "length": fileSize})
438
mpagenko5b5cb982021-08-18 16:38:51 +0000439 dm.updateFileState(ctx, aFileName, fileSize)
mpagenkoc26d4c02021-05-06 14:27:57 +0000440 //TODO:!!! further extension could be provided here, e.g. already computing and possibly comparing the CRC, vendor check
441 }()
442 return nil
443}
mpagenkoaa3afe92021-05-21 16:20:58 +0000444
Joey Armstrong89c812c2024-01-12 19:00:20 -0500445// removeImage deletes the given image according to the Image name from filesystem and downloadImageDscSlice
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000446func (dm *FileDownloadManager) removeImage(ctx context.Context, aImageName string, aDelFs bool) {
mpagenkoc497ee32021-11-10 17:30:20 +0000447 logger.Debugw(ctx, "remove the image from Adapter", log.Fields{"image-name": aImageName, "del-fs": aDelFs})
mpagenkoaa3afe92021-05-21 16:20:58 +0000448 dm.mutexDownloadImageDsc.RLock()
449 defer dm.mutexDownloadImageDsc.RUnlock()
450
451 tmpSlice := dm.downloadImageDscSlice[:0]
452 for _, dnldImgDsc := range dm.downloadImageDscSlice {
453 if dnldImgDsc.downloadImageName == aImageName {
mpagenko38662d02021-08-11 09:45:19 +0000454 //image found (by name)
mpagenkoaa3afe92021-05-21 16:20:58 +0000455 logger.Debugw(ctx, "removing image", log.Fields{"image-name": aImageName})
mpagenko38662d02021-08-11 09:45:19 +0000456 if aDelFs {
457 //remove the image from filesystem
458 aLocalPathName := cDefaultLocalDir + "/" + aImageName
459 if err := os.Remove(aLocalPathName); err != nil {
460 // might be a temporary situation, when the file was not yet (completely) written
461 logger.Debugw(ctx, "image not removed from filesystem", log.Fields{
462 "image-name": aImageName, "error": err})
463 }
mpagenkoaa3afe92021-05-21 16:20:58 +0000464 }
mpagenko38662d02021-08-11 09:45:19 +0000465 // and remove from the imageDsc slice by just not appending
mpagenkoaa3afe92021-05-21 16:20:58 +0000466 } else {
467 tmpSlice = append(tmpSlice, dnldImgDsc)
468 }
469 }
470 dm.downloadImageDscSlice = tmpSlice
471 //image not found (by name)
472}
mpagenko38662d02021-08-11 09:45:19 +0000473
Joey Armstrong89c812c2024-01-12 19:00:20 -0500474// CancelDownload stops the download and clears all entires concerning this aimageName
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000475func (dm *FileDownloadManager) CancelDownload(ctx context.Context, aImageName string) {
mpagenko38662d02021-08-11 09:45:19 +0000476 // for the moment that would only support to wait for the download end and remove the image then
477 // 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 +0000478 dm.mutexDownloadImageDsc.RLock()
479 for imgKey, dnldImgDsc := range dm.downloadImageDscSlice {
480 if dnldImgDsc.downloadImageName == aImageName {
481 //image found (by name) - need to to check on ongoing download
482 if dm.downloadImageDscSlice[imgKey].downloadActive {
483 //then cancel the download using the context cancel function
484 dm.downloadImageDscSlice[imgKey].downloadContextCancelFn()
485 }
486 //and remove possibly stored traces of this image
487 dm.mutexDownloadImageDsc.RUnlock()
488 go dm.removeImage(ctx, aImageName, true) //including the chance that nothing was yet written to FS, should not matter
489 return //can leave directly
490 }
491 }
492 dm.mutexDownloadImageDsc.RUnlock()
mpagenko38662d02021-08-11 09:45:19 +0000493}