blob: e30d329d662e0db65385cf9a096101e8f4013384 [file] [log] [blame]
mpagenkoc8bba412021-01-15 15:38:44 +00001/*
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
18package adaptercoreonu
19
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"
mpagenkoc8bba412021-01-15 15:38:44 +000029 "sync"
mpagenko02cf1b22021-03-12 17:30:30 +000030 "time"
mpagenkoc8bba412021-01-15 15:38:44 +000031
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
46type adapterDownloadManager struct {
47 mutexDownloadImageDsc sync.RWMutex
48 downloadImageDscSlice []*voltha.ImageDownload
mpagenko80622a52021-02-09 16:53:23 +000049 // maybe just for test purpose
50 arrayFileFragment [32]byte
mpagenkoc8bba412021-01-15 15:38:44 +000051}
52
53//newAdapterDownloadManager constructor returns a new instance of a adapterDownloadManager
54//mib_db (as well as not inluded alarm_db not really used in this code? VERIFY!!)
55func newAdapterDownloadManager(ctx context.Context) *adapterDownloadManager {
56 logger.Debug(ctx, "init-adapterDownloadManager")
57 var localDnldMgr adapterDownloadManager
58 localDnldMgr.downloadImageDscSlice = make([]*voltha.ImageDownload, 0)
mpagenkoc8bba412021-01-15 15:38:44 +000059 return &localDnldMgr
60}
61
62//imageExists returns true if the requested image already exists within the adapter
63func (dm *adapterDownloadManager) imageExists(ctx context.Context, apImageDsc *voltha.ImageDownload) bool {
64 logger.Debugw(ctx, "checking on existence of the image", log.Fields{"image-name": (*apImageDsc).Name})
65 dm.mutexDownloadImageDsc.RLock()
66 defer dm.mutexDownloadImageDsc.RUnlock()
67
68 for _, pDnldImgDsc := range dm.downloadImageDscSlice {
69 if (*pDnldImgDsc).Name == (*apImageDsc).Name {
70 //image found (by name)
71 return true
72 }
73 }
74 //image not found (by name)
75 return false
76}
77
mpagenko80622a52021-02-09 16:53:23 +000078//imageLocallyDownloaded returns true if the requested image already exists within the adapter
79func (dm *adapterDownloadManager) imageLocallyDownloaded(ctx context.Context, apImageDsc *voltha.ImageDownload) bool {
80 logger.Debugw(ctx, "checking if image is fully downloaded", log.Fields{"image-name": (*apImageDsc).Name})
81 dm.mutexDownloadImageDsc.RLock()
82 defer dm.mutexDownloadImageDsc.RUnlock()
83
84 for _, pDnldImgDsc := range dm.downloadImageDscSlice {
85 if (*pDnldImgDsc).Name == (*apImageDsc).Name {
86 //image found (by name)
87 if (*pDnldImgDsc).DownloadState == voltha.ImageDownload_DOWNLOAD_SUCCEEDED {
88 logger.Debugw(ctx, "image has been fully downloaded", log.Fields{"image-name": (*apImageDsc).Name})
89 return true
90 }
91 logger.Debugw(ctx, "image not yet fully downloaded", log.Fields{"image-name": (*apImageDsc).Name})
92 return false
93 }
94 }
95 //image not found (by name)
96 logger.Errorw(ctx, "image does not exist", log.Fields{"image-name": (*apImageDsc).Name})
97 return false
98}
99
mpagenkoc8bba412021-01-15 15:38:44 +0000100//startDownload returns true if the download of the requested image could be started
101func (dm *adapterDownloadManager) startDownload(ctx context.Context, apImageDsc *voltha.ImageDownload) error {
mpagenko15ff4a52021-03-02 10:09:20 +0000102 if apImageDsc.LocalDir != "" {
mpagenko02cf1b22021-03-12 17:30:30 +0000103 logger.Infow(ctx, "image download-to-adapter requested", log.Fields{
104 "image-path": apImageDsc.LocalDir, "image-name": apImageDsc.Name})
mpagenko15ff4a52021-03-02 10:09:20 +0000105 newImageDscPos := len(dm.downloadImageDscSlice)
106 dm.downloadImageDscSlice = append(dm.downloadImageDscSlice, apImageDsc)
107 dm.downloadImageDscSlice[newImageDscPos].DownloadState = voltha.ImageDownload_DOWNLOAD_STARTED
mpagenko02cf1b22021-03-12 17:30:30 +0000108 if apImageDsc.LocalDir == "/intern" {
109 //just for initial 'internal' test verification
110 //just some basic test file simulation
111 dm.downloadImageDscSlice[newImageDscPos].LocalDir = "/tmp"
112 go dm.writeFileToLFS(ctx, "/tmp", apImageDsc.Name)
113 return nil
114 } else if apImageDsc.LocalDir == "/reboot" {
115 dm.downloadImageDscSlice[newImageDscPos].LocalDir = "/tmp"
116 }
117 //try to download from http
118 urlName := apImageDsc.Url + "/" + apImageDsc.Name
mpagenkobf67a092021-03-17 09:52:28 +0000119 err := dm.downloadFile(ctx, urlName, apImageDsc.LocalDir, apImageDsc.Name)
120 if err != nil {
121 return (err)
122 }
mpagenko15ff4a52021-03-02 10:09:20 +0000123 //return success to comfort the core processing during integration
124 return nil
125 }
126 // we can use the missing local path temporary also to test some failure behavior (system reation on failure)
127 // with updated control API's or at some adequate time we could also set some defined fixed localPath internally
128 logger.Errorw(ctx, "could not start download: no valid local directory to write to", log.Fields{"image-name": (*apImageDsc).Name})
129 return errors.New("could not start download: no valid local directory to write to")
mpagenkoc8bba412021-01-15 15:38:44 +0000130}
mpagenko80622a52021-02-09 16:53:23 +0000131
mpagenko02cf1b22021-03-12 17:30:30 +0000132//downloadFile downloads the specified file from the given http location
mpagenkobf67a092021-03-17 09:52:28 +0000133func (dm *adapterDownloadManager) downloadFile(ctx context.Context, aURLName string, aFilePath string, aFileName string) error {
mpagenko02cf1b22021-03-12 17:30:30 +0000134 // Get the data
135 logger.Infow(ctx, "downloading from http", log.Fields{"url": aURLName, "localPath": aFilePath})
136 // http command is already part of the aURLName argument
137 urlBase, err1 := url.Parse(aURLName)
138 if err1 != nil {
139 logger.Errorw(ctx, "could not set base url command", log.Fields{"url": aURLName, "error": err1})
mpagenkobf67a092021-03-17 09:52:28 +0000140 return fmt.Errorf("could not set base url command: %s, error: %s", aURLName, err1)
mpagenko02cf1b22021-03-12 17:30:30 +0000141 }
142 urlParams := url.Values{}
143 urlBase.RawQuery = urlParams.Encode()
mpagenko02cf1b22021-03-12 17:30:30 +0000144
mpagenkobf67a092021-03-17 09:52:28 +0000145 //pre-check on file existence
146 reqExist, errExist2 := http.NewRequest("HEAD", urlBase.String(), nil)
147 if errExist2 != nil {
148 logger.Errorw(ctx, "could not generate http head request", log.Fields{"url": urlBase.String(), "error": errExist2})
149 return fmt.Errorf("could not generate http head request: %s, error: %s", aURLName, errExist2)
150 }
151 ctxExist, cancelExist := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) //waiting for some fast answer
152 defer cancelExist()
153 _ = reqExist.WithContext(ctxExist)
154 respExist, errExist3 := http.DefaultClient.Do(reqExist)
155 if errExist3 != nil || respExist.StatusCode != http.StatusOK {
156 logger.Infow(ctx, "could not http head from url", log.Fields{"url": urlBase.String(),
157 "error": errExist3, "status": respExist.StatusCode})
158 //if head is not supported by server we cannot use this test and just try to continue
159 if respExist.StatusCode != http.StatusMethodNotAllowed {
160 logger.Errorw(ctx, "http head from url: file does not exist here, aborting", log.Fields{"url": urlBase.String(),
161 "error": errExist3, "status": respExist.StatusCode})
162 return fmt.Errorf("http head from url: file does not exist here, aborting: %s, error: %s, status: %d",
163 aURLName, errExist2, respExist.StatusCode)
164 }
mpagenko02cf1b22021-03-12 17:30:30 +0000165 }
166 defer func() {
mpagenkobf67a092021-03-17 09:52:28 +0000167 deferredErr := respExist.Body.Close()
mpagenko02cf1b22021-03-12 17:30:30 +0000168 if deferredErr != nil {
mpagenkobf67a092021-03-17 09:52:28 +0000169 logger.Errorw(ctx, "error at closing http head response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
mpagenko02cf1b22021-03-12 17:30:30 +0000170 }
171 }()
172
mpagenkobf67a092021-03-17 09:52:28 +0000173 //trying to download - do it in background as it may take some time ...
174 go func() {
175 req, err2 := http.NewRequest("GET", urlBase.String(), nil)
176 if err2 != nil {
177 logger.Errorw(ctx, "could not generate http request", log.Fields{"url": urlBase.String(), "error": err2})
178 return
179 }
180 ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) //long timeout for remote server and big file
181 defer cancel()
182 _ = req.WithContext(ctx)
183 resp, err3 := http.DefaultClient.Do(req)
184 if err3 != nil || respExist.StatusCode != http.StatusOK {
185 logger.Errorw(ctx, "could not http get from url", log.Fields{"url": urlBase.String(),
186 "error": err3, "status": respExist.StatusCode})
187 return
188 }
189 defer func() {
190 deferredErr := resp.Body.Close()
191 if deferredErr != nil {
192 logger.Errorw(ctx, "error at closing http get response body", log.Fields{"url": urlBase.String(), "error": deferredErr})
193 }
194 }()
195
196 // Create the file
197 aLocalPathName := aFilePath + "/" + aFileName
198 file, err := os.Create(aLocalPathName)
199 if err != nil {
200 logger.Errorw(ctx, "could not create local file", log.Fields{"path_file": aLocalPathName, "error": err})
201 return
202 }
203 defer func() {
204 deferredErr := file.Close()
205 if deferredErr != nil {
206 logger.Errorw(ctx, "error at closing new file", log.Fields{"path_file": aLocalPathName, "error": deferredErr})
207 }
208 }()
209
210 // Write the body to file
211 _, err = io.Copy(file, resp.Body)
212 if err != nil {
213 logger.Errorw(ctx, "could not copy file content", log.Fields{"url": urlBase.String(), "file": aLocalPathName, "error": err})
214 return
215 }
216
217 fileStats, statsErr := file.Stat()
218 if err != nil {
219 logger.Errorw(ctx, "created file can't be accessed", log.Fields{"file": aLocalPathName, "stat-error": statsErr})
220 }
221 logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPathName, "length": fileStats.Size()})
222
223 for _, pDnldImgDsc := range dm.downloadImageDscSlice {
224 if (*pDnldImgDsc).Name == aFileName {
225 //image found (by name)
226 (*pDnldImgDsc).DownloadState = voltha.ImageDownload_DOWNLOAD_SUCCEEDED
227 return //can leave directly
228 }
mpagenko02cf1b22021-03-12 17:30:30 +0000229 }
230 }()
mpagenkobf67a092021-03-17 09:52:28 +0000231 return nil
mpagenko02cf1b22021-03-12 17:30:30 +0000232}
233
mpagenko80622a52021-02-09 16:53:23 +0000234//writeFileToLFS writes the downloaded file to the local file system
mpagenko02cf1b22021-03-12 17:30:30 +0000235// this is just an internal test function and can be removed if other download capabilities exist
236func (dm *adapterDownloadManager) writeFileToLFS(ctx context.Context, aLocalPath string, aFileName string) {
mpagenko80622a52021-02-09 16:53:23 +0000237 // by now just a simulation to write a file with predefined 'variable' content
238 totalFileLength := 0
mpagenko15ff4a52021-03-02 10:09:20 +0000239 logger.Debugw(ctx, "writing fixed size simulation file locally", log.Fields{
mpagenko80622a52021-02-09 16:53:23 +0000240 "image-name": aFileName, "image-path": aLocalPath})
241 file, err := os.Create(aLocalPath + "/" + aFileName)
242 if err == nil {
243 // write 32KB test file
244 for totalFileLength < 32*1024 {
245 if written, wrErr := file.Write(dm.getIncrementalSliceContent(ctx)); wrErr == nil {
246 totalFileLength += written
247 } else {
mpagenko15ff4a52021-03-02 10:09:20 +0000248 logger.Errorw(ctx, "could not write to file", log.Fields{"create-error": wrErr})
mpagenko80622a52021-02-09 16:53:23 +0000249 break //stop writing
250 }
251 }
252 } else {
mpagenko15ff4a52021-03-02 10:09:20 +0000253 logger.Errorw(ctx, "could not create file", log.Fields{"create-error": err})
mpagenko80622a52021-02-09 16:53:23 +0000254 }
255
256 fileStats, statsErr := file.Stat()
257 if err != nil {
258 logger.Errorw(ctx, "created file can't be accessed", log.Fields{"stat-error": statsErr})
259 }
mpagenko15ff4a52021-03-02 10:09:20 +0000260 logger.Infow(ctx, "written file size is", log.Fields{"file": aLocalPath + "/" + aFileName, "length": fileStats.Size()})
mpagenko02cf1b22021-03-12 17:30:30 +0000261
262 defer func() {
263 deferredErr := file.Close()
264 if deferredErr != nil {
265 logger.Errorw(ctx, "error at closing test file", log.Fields{"file": aLocalPath + "/" + aFileName, "error": deferredErr})
266 }
267 }()
mpagenko80622a52021-02-09 16:53:23 +0000268
269 for _, pDnldImgDsc := range dm.downloadImageDscSlice {
270 if (*pDnldImgDsc).Name == aFileName {
271 //image found (by name)
272 (*pDnldImgDsc).DownloadState = voltha.ImageDownload_DOWNLOAD_SUCCEEDED
273 return //can leave directly
274 }
275 }
276}
277
278//getImageBufferLen returns the length of the specified file in bytes (file size)
279func (dm *adapterDownloadManager) getImageBufferLen(ctx context.Context, aFileName string,
280 aLocalPath string) (int64, error) {
281 //maybe we can also use FileSize from dm.downloadImageDscSlice - future option?
282
283 //nolint:gosec
284 file, err := os.Open(aLocalPath + "/" + aFileName)
285 if err != nil {
286 return 0, err
287 }
288 //nolint:errcheck
289 defer file.Close()
290
291 stats, statsErr := file.Stat()
292 if statsErr != nil {
293 return 0, statsErr
294 }
295
296 return stats.Size(), nil
297}
298
299//getDownloadImageBuffer returns the content of the requested file as byte slice
300func (dm *adapterDownloadManager) getDownloadImageBuffer(ctx context.Context, aFileName string,
301 aLocalPath string) ([]byte, error) {
302 //nolint:gosec
303 file, err := os.Open(aLocalPath + "/" + aFileName)
304 if err != nil {
305 return nil, err
306 }
307 //nolint:errcheck
308 defer file.Close()
309
310 stats, statsErr := file.Stat()
311 if statsErr != nil {
312 return nil, statsErr
313 }
314
315 var size int64 = stats.Size()
316 bytes := make([]byte, size)
317
318 buffer := bufio.NewReader(file)
319 _, err = buffer.Read(bytes)
320
321 return bytes, err
322}
323
324//getIncrementalSliceContent returns a byte slice of incremented bytes of internal array (used for file emulation)
325// (used for file emulation)
326func (dm *adapterDownloadManager) getIncrementalSliceContent(ctx context.Context) []byte {
327 lastValue := dm.arrayFileFragment[len(dm.arrayFileFragment)-1]
328 for index := range dm.arrayFileFragment {
329 lastValue++
330 dm.arrayFileFragment[index] = lastValue
331 }
332 return dm.arrayFileFragment[:]
333}