blob: 951d6e3606cfecdf1a17d8a4fbee21708a1091db [file] [log] [blame]
mpagenkoc8bba412021-01-15 15:38:44 +00001/*
Joey Armstronge8c091f2023-01-17 16:56:26 -05002 * Copyright 2020-2023 Open Networking Foundation (ONF) and the ONF Contributors
mpagenkoc8bba412021-01-15 15:38:44 +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
mpagenkoc8bba412021-01-15 15:38:44 +000019
20import (
mpagenko80622a52021-02-09 16:53:23 +000021 "bufio"
mpagenkoc8bba412021-01-15 15:38:44 +000022 "context"
mpagenko15ff4a52021-03-02 10:09:20 +000023 "errors"
mpagenkobf67a092021-03-17 09:52:28 +000024 "fmt"
mpagenko02cf1b22021-03-12 17:30:30 +000025 "io"
26 "net/http"
27 "net/url"
mpagenko80622a52021-02-09 16:53:23 +000028 "os"
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +030029 "path/filepath"
mpagenkoc8bba412021-01-15 15:38:44 +000030 "sync"
mpagenko02cf1b22021-03-12 17:30:30 +000031 "time"
mpagenkoc8bba412021-01-15 15:38:44 +000032
khenaidoo7d3c5582021-08-11 18:09:44 -040033 "github.com/opencord/voltha-protos/v5/go/voltha"
mpagenkoc8bba412021-01-15 15:38:44 +000034
khenaidoo7d3c5582021-08-11 18:09:44 -040035 "github.com/opencord/voltha-lib-go/v7/pkg/log"
mpagenkoc8bba412021-01-15 15:38:44 +000036)
37
38// ### downloadToAdapter related definitions ####
39
40//not yet defined to go with sca..., later also some configure options ??
41//const defaultDownloadTimeout = 60 // (?) Seconds
42//const localImgPath = "/home/lcui/work/tmp"
43
44// ### downloadToAdapter - end ####
45
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000046//AdapterDownloadManager structure holds information needed for downloading to and storing images within the adapter
47type AdapterDownloadManager struct {
mpagenkoc8bba412021-01-15 15:38:44 +000048 mutexDownloadImageDsc sync.RWMutex
49 downloadImageDscSlice []*voltha.ImageDownload
50}
51
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000052//NewAdapterDownloadManager constructor returns a new instance of a AdapterDownloadManager
mpagenkoc8bba412021-01-15 15:38:44 +000053//mib_db (as well as not inluded alarm_db not really used in this code? VERIFY!!)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000054func NewAdapterDownloadManager(ctx context.Context) *AdapterDownloadManager {
55 logger.Debug(ctx, "init-AdapterDownloadManager")
56 var localDnldMgr AdapterDownloadManager
mpagenkoc8bba412021-01-15 15:38:44 +000057 localDnldMgr.downloadImageDscSlice = make([]*voltha.ImageDownload, 0)
mpagenkoc8bba412021-01-15 15:38:44 +000058 return &localDnldMgr
59}
60
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000061//ImageExists returns true if the requested image already exists within the adapter
62func (dm *AdapterDownloadManager) ImageExists(ctx context.Context, apImageDsc *voltha.ImageDownload) bool {
mpagenkoc8bba412021-01-15 15:38:44 +000063 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
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000077//ImageLocallyDownloaded returns true if the requested image already exists within the adapter
78func (dm *AdapterDownloadManager) ImageLocallyDownloaded(ctx context.Context, apImageDsc *voltha.ImageDownload) bool {
mpagenko80622a52021-02-09 16:53:23 +000079 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
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +000099//StartDownload returns true if the download of the requested image could be started
100func (dm *AdapterDownloadManager) StartDownload(ctx context.Context, apImageDsc *voltha.ImageDownload) error {
mpagenko15ff4a52021-03-02 10:09:20 +0000101 if apImageDsc.LocalDir != "" {
mpagenko02cf1b22021-03-12 17:30:30 +0000102 logger.Infow(ctx, "image download-to-adapter requested", log.Fields{
103 "image-path": apImageDsc.LocalDir, "image-name": apImageDsc.Name})
mpagenko15ff4a52021-03-02 10:09:20 +0000104 newImageDscPos := len(dm.downloadImageDscSlice)
105 dm.downloadImageDscSlice = append(dm.downloadImageDscSlice, apImageDsc)
106 dm.downloadImageDscSlice[newImageDscPos].DownloadState = voltha.ImageDownload_DOWNLOAD_STARTED
mpagenko02cf1b22021-03-12 17:30:30 +0000107 //try to download from http
108 urlName := apImageDsc.Url + "/" + apImageDsc.Name
mpagenkobf67a092021-03-17 09:52:28 +0000109 err := dm.downloadFile(ctx, urlName, apImageDsc.LocalDir, apImageDsc.Name)
110 if err != nil {
111 return (err)
112 }
mpagenko15ff4a52021-03-02 10:09:20 +0000113 //return success to comfort the core processing during integration
114 return nil
115 }
116 // we can use the missing local path temporary also to test some failure behavior (system reation on failure)
117 // with updated control API's or at some adequate time we could also set some defined fixed localPath internally
118 logger.Errorw(ctx, "could not start download: no valid local directory to write to", log.Fields{"image-name": (*apImageDsc).Name})
119 return errors.New("could not start download: no valid local directory to write to")
mpagenkoc8bba412021-01-15 15:38:44 +0000120}
mpagenko80622a52021-02-09 16:53:23 +0000121
mpagenko02cf1b22021-03-12 17:30:30 +0000122//downloadFile downloads the specified file from the given http location
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000123func (dm *AdapterDownloadManager) downloadFile(ctx context.Context, aURLName string, aFilePath string, aFileName string) error {
mpagenko02cf1b22021-03-12 17:30:30 +0000124 // Get the data
125 logger.Infow(ctx, "downloading from http", log.Fields{"url": aURLName, "localPath": aFilePath})
126 // http command is already part of the aURLName argument
127 urlBase, err1 := url.Parse(aURLName)
128 if err1 != nil {
129 logger.Errorw(ctx, "could not set base url command", log.Fields{"url": aURLName, "error": err1})
mpagenkobf67a092021-03-17 09:52:28 +0000130 return fmt.Errorf("could not set base url command: %s, error: %s", aURLName, err1)
mpagenko02cf1b22021-03-12 17:30:30 +0000131 }
132 urlParams := url.Values{}
133 urlBase.RawQuery = urlParams.Encode()
mpagenko02cf1b22021-03-12 17:30:30 +0000134
mpagenkobf67a092021-03-17 09:52:28 +0000135 //pre-check on file existence
136 reqExist, errExist2 := http.NewRequest("HEAD", urlBase.String(), nil)
137 if errExist2 != nil {
138 logger.Errorw(ctx, "could not generate http head request", log.Fields{"url": urlBase.String(), "error": errExist2})
139 return fmt.Errorf("could not generate http head request: %s, error: %s", aURLName, errExist2)
140 }
141 ctxExist, cancelExist := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) //waiting for some fast answer
142 defer cancelExist()
143 _ = reqExist.WithContext(ctxExist)
144 respExist, errExist3 := http.DefaultClient.Do(reqExist)
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300145 if errExist3 != nil || (respExist != nil && respExist.StatusCode != http.StatusOK) {
146 if respExist != nil {
147 logger.Errorw(ctx, "could not http head from url", log.Fields{"url": urlBase.String(),
mpagenkobf67a092021-03-17 09:52:28 +0000148 "error": errExist3, "status": respExist.StatusCode})
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300149 //if head is not supported by server we cannot use this test and just try to continue
150 if respExist.StatusCode != http.StatusMethodNotAllowed {
151 logger.Errorw(ctx, "http head from url: file does not exist here, aborting", log.Fields{"url": urlBase.String(),
152 "error": errExist3, "status": respExist.StatusCode})
153 return fmt.Errorf("http head from url: file does not exist here, aborting: %s, error: %s, status: %d",
154 aURLName, errExist2, respExist.StatusCode)
155 }
156 } else {
157 logger.Errorw(ctx, "could not http head from url", log.Fields{"url": urlBase.String(),
158 "error": errExist3})
mpagenkobf67a092021-03-17 09:52:28 +0000159 }
mpagenko02cf1b22021-03-12 17:30:30 +0000160 }
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300161
162 if errExist3 == nil && respExist != nil {
163 defer func() {
164 deferredErr := respExist.Body.Close()
165 if deferredErr != nil {
166 logger.Errorw(ctx, "error at closing http head response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
167 }
168 }()
169 }
mpagenko02cf1b22021-03-12 17:30:30 +0000170
mpagenkobf67a092021-03-17 09:52:28 +0000171 //trying to download - do it in background as it may take some time ...
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300172 go dm.requestDownload(ctx, urlBase, aFilePath, aFileName)
173 return nil
174}
mpagenkobf67a092021-03-17 09:52:28 +0000175
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000176func (dm *AdapterDownloadManager) requestDownload(ctx context.Context, urlBase *url.URL, aFilePath, aFileName string) {
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300177 req, err2 := http.NewRequest("GET", urlBase.String(), nil)
178 if err2 != nil {
179 logger.Errorw(ctx, "could not generate http request", log.Fields{"url": urlBase.String(), "error": err2})
180 return
181 }
182 ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) //long timeout for remote server and big file
183 defer cancel()
184 _ = req.WithContext(ctx)
185 resp, err3 := http.DefaultClient.Do(req)
186 if err3 != nil {
187 logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(), "error": err3})
188 return
189 }
190 defer func() {
191 deferredErr := resp.Body.Close()
192 if deferredErr != nil {
193 logger.Errorw(ctx, "error at closing http get response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
mpagenko02cf1b22021-03-12 17:30:30 +0000194 }
195 }()
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300196
197 if resp.StatusCode != http.StatusOK {
198 logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(), "status": resp.StatusCode})
199 return
200 }
201
202 // Create the file
203 aLocalPathName := aFilePath + "/" + aFileName
204 file, err := os.Create(aLocalPathName)
205 if err != nil {
206 logger.Errorw(ctx, "could not create local file", log.Fields{"path_file": aLocalPathName, "error": err})
207 return
208 }
209 defer func() {
210 deferredErr := file.Close()
211 if deferredErr != nil {
212 logger.Errorw(ctx, "error at closing new file", log.Fields{"path_file": aLocalPathName, "error": deferredErr})
213 }
214 }()
215
216 // Write the body to file
217 _, err = io.Copy(file, resp.Body)
218 if err != nil {
219 logger.Errorw(ctx, "could not copy file content", log.Fields{"url": urlBase.String(), "file": aLocalPathName, "error": err})
220 return
221 }
222
223 fileStats, statsErr := file.Stat()
224 if statsErr != nil {
225 logger.Errorw(ctx, "created file can't be accessed", log.Fields{"file": aLocalPathName, "stat-error": statsErr})
226 }
227 if fileStats != nil {
228 logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPathName, "length": fileStats.Size()})
229 }
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 }
mpagenko02cf1b22021-03-12 17:30:30 +0000238}
239
mpagenko80622a52021-02-09 16:53:23 +0000240//getImageBufferLen returns the length of the specified file in bytes (file size)
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000241func (dm *AdapterDownloadManager) getImageBufferLen(ctx context.Context, aFileName string,
mpagenko80622a52021-02-09 16:53:23 +0000242 aLocalPath string) (int64, error) {
243 //maybe we can also use FileSize from dm.downloadImageDscSlice - future option?
244
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300245 file, err := os.Open(filepath.Clean(aLocalPath + "/" + aFileName))
mpagenko80622a52021-02-09 16:53:23 +0000246 if err != nil {
247 return 0, err
248 }
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300249 defer func() {
250 err := file.Close()
251 if err != nil {
252 logger.Errorw(ctx, "failed to close file", log.Fields{"error": err})
253 }
254 }()
mpagenko80622a52021-02-09 16:53:23 +0000255
256 stats, statsErr := file.Stat()
257 if statsErr != nil {
258 return 0, statsErr
259 }
260
261 return stats.Size(), nil
262}
263
264//getDownloadImageBuffer returns the content of the requested file as byte slice
Holger Hildebrandt4b5e73f2021-08-19 06:51:21 +0000265func (dm *AdapterDownloadManager) getDownloadImageBuffer(ctx context.Context, aFileName string,
mpagenko80622a52021-02-09 16:53:23 +0000266 aLocalPath string) ([]byte, error) {
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300267 file, err := os.Open(filepath.Clean(aLocalPath + "/" + aFileName))
mpagenko80622a52021-02-09 16:53:23 +0000268 if err != nil {
269 return nil, err
270 }
Andrey Pozolotin1394a1c2021-06-01 00:54:18 +0300271 defer func() {
272 err := file.Close()
273 if err != nil {
274 logger.Errorw(ctx, "failed to close file", log.Fields{"error": err})
275 }
276 }()
mpagenko80622a52021-02-09 16:53:23 +0000277
278 stats, statsErr := file.Stat()
279 if statsErr != nil {
280 return nil, statsErr
281 }
282
283 var size int64 = stats.Size()
284 bytes := make([]byte, size)
285
286 buffer := bufio.NewReader(file)
287 _, err = buffer.Read(bytes)
288
289 return bytes, err
290}