Use go module as dependency tool instead of glide
[src/xds/xds-server.git] / lib / xdsserver / sdk.go
1 /*
2  * Copyright (C) 2017-2018 "IoT.bzh"
3  * Author Sebastien Douheret <sebastien@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package xdsserver
19
20 import (
21         "encoding/json"
22         "fmt"
23         "os/exec"
24         "path"
25         "strconv"
26         "strings"
27         "time"
28
29         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
30         "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/eows"
31         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
32         "github.com/Sirupsen/logrus"
33         uuid "github.com/satori/go.uuid"
34 )
35
36 // Definition of scripts used to managed SDKs
37 const (
38         scriptAdd          = "add"
39         scriptDbDump       = "db-dump"
40         scriptDbUpdate     = "db-update"
41         scriptGetFamConfig = "get-family-config"
42         scriptGetSdkInfo   = "get-sdk-info"
43         scriptRemove       = "remove"
44 )
45
46 var scriptsAll = []string{
47         scriptAdd,
48         scriptDbDump,
49         scriptDbUpdate,
50         scriptGetFamConfig,
51         scriptGetSdkInfo,
52         scriptRemove,
53 }
54
55 var sdkCmdID = 0
56
57 // CrossSDK Hold SDK config
58 type CrossSDK struct {
59         *Context
60         sdk        xsapiv1.SDK
61         scripts    map[string]string
62         installCmd *eows.ExecOverWS
63         removeCmd  *eows.ExecOverWS
64 }
65
66 // ListCrossSDK List all available and installed SDK  (call "db-dump" script)
67 func ListCrossSDK(scriptDir string, update bool, log *logrus.Logger) ([]xsapiv1.SDK, error) {
68         sdksList := []xsapiv1.SDK{}
69
70         // First update sdk DB when requested
71         if update {
72                 out, err := UpdateSDKDb(scriptDir, log)
73                 if err != nil {
74                         log.Errorf("SDK DB update failure (%v): %v", err, out)
75                         return sdksList, fmt.Errorf("Error while updating SDK DB (%v)", err)
76                 }
77         }
78
79         // Retrieve SDKs list and info
80         cmd := exec.Command(path.Join(scriptDir, scriptDbDump))
81         stdout, err := cmd.CombinedOutput()
82         if err != nil {
83                 return sdksList, fmt.Errorf("Cannot get sdks list: %v", err)
84         }
85
86         if err = json.Unmarshal(stdout, &sdksList); err != nil {
87                 log.Errorf("SDK %s script output:\n%v\n", scriptDbDump, string(stdout))
88                 return sdksList, fmt.Errorf("Cannot decode sdk list %v", err)
89         }
90
91         return sdksList, nil
92 }
93
94 // GetSDKInfo Used get-sdk-info script to extract SDK get info from a SDK file/tarball
95 func GetSDKInfo(scriptDir, url, filename, md5sum, uuid string, log *logrus.Logger) (xsapiv1.SDK, error) {
96         sdk := xsapiv1.SDK{}
97
98         args := []string{}
99         if url != "" {
100                 args = append(args, "--url", url)
101         } else if filename != "" {
102                 args = append(args, "--file", filename)
103                 if md5sum != "" {
104                         args = append(args, "--md5", md5sum)
105                 }
106         } else {
107                 return sdk, fmt.Errorf("url of filename must be set")
108         }
109         if uuid != "" {
110                 args = append(args, "--uuid", uuid)
111         }
112
113         cmd := exec.Command(path.Join(scriptDir, scriptGetSdkInfo), args...)
114         stdout, err := cmd.CombinedOutput()
115         if err != nil {
116                 return sdk, fmt.Errorf("%v %v", string(stdout), err)
117         }
118
119         if err = json.Unmarshal(stdout, &sdk); err != nil {
120                 log.Errorf("SDK %s script output:\n%v\n", scriptGetSdkInfo, string(stdout))
121                 return sdk, fmt.Errorf("Cannot decode sdk info %v", err)
122         }
123         return sdk, nil
124 }
125
126 // UpdateSDKDb Used db-update script to update SDK database
127 func UpdateSDKDb(scriptDir string, log *logrus.Logger) (string, error) {
128         args := []string{}
129         cmd := exec.Command(path.Join(scriptDir, scriptDbUpdate), args...)
130         stdout, err := cmd.CombinedOutput()
131
132         return string(stdout), err
133 }
134
135 // NewCrossSDK creates a new instance of CrossSDK
136 func NewCrossSDK(ctx *Context, sdk xsapiv1.SDK, scriptDir string) (*CrossSDK, error) {
137         s := CrossSDK{
138                 Context: ctx,
139                 sdk:     sdk,
140                 scripts: make(map[string]string),
141         }
142
143         // Execute get-config script to retrieve SDK configuration
144         getConfFile := path.Join(scriptDir, scriptGetFamConfig)
145         if !common.Exists(getConfFile) {
146                 return &s, fmt.Errorf("'%s' script file not found in %s", scriptGetFamConfig, scriptDir)
147         }
148
149         cmd := exec.Command(getConfFile)
150         stdout, err := cmd.CombinedOutput()
151         if err != nil {
152                 return &s, fmt.Errorf("Cannot get sdk config using %s: %v", getConfFile, err)
153         }
154
155         err = json.Unmarshal(stdout, &s.sdk.FamilyConf)
156         if err != nil {
157                 s.Log.Errorf("SDK config script output:\n%v\n", string(stdout))
158                 return &s, fmt.Errorf("Cannot decode sdk config %v", err)
159         }
160         famName := s.sdk.FamilyConf.FamilyName
161
162         // Sanity check
163         if s.sdk.FamilyConf.RootDir == "" {
164                 return &s, fmt.Errorf("SDK config not valid (rootDir not set)")
165         }
166         if s.sdk.FamilyConf.EnvSetupFile == "" {
167                 return &s, fmt.Errorf("SDK config not valid (envSetupFile not set)")
168         }
169
170         // Check that other mandatory scripts are present
171         for _, scr := range scriptsAll {
172                 s.scripts[scr] = path.Join(scriptDir, scr)
173                 if !common.Exists(s.scripts[scr]) {
174                         return &s, fmt.Errorf("Script named '%s' missing in SDK family '%s'", scr, famName)
175                 }
176         }
177
178         // Fixed default fields value
179         sdk.LastError = ""
180         if sdk.Status == "" {
181                 sdk.Status = xsapiv1.SdkStatusNotInstalled
182         }
183
184         // Sanity check
185         errMsg := "Invalid SDK definition "
186         if sdk.Name == "" {
187                 return &s, fmt.Errorf(errMsg + "(name not set)")
188         } else if sdk.Profile == "" {
189                 return &s, fmt.Errorf(errMsg + "(profile not set)")
190         } else if sdk.Version == "" {
191                 return &s, fmt.Errorf(errMsg + "(version not set)")
192         } else if sdk.Arch == "" {
193                 return &s, fmt.Errorf(errMsg + "(arch not set)")
194         }
195         if sdk.Status == xsapiv1.SdkStatusInstalled {
196                 if sdk.SetupFile == "" {
197                         return &s, fmt.Errorf(errMsg + "(setupFile not set)")
198                 } else if !common.Exists(sdk.SetupFile) {
199                         return &s, fmt.Errorf(errMsg + "(setupFile not accessible)")
200                 }
201                 if sdk.Path == "" {
202                         return &s, fmt.Errorf(errMsg + "(path not set)")
203                 } else if !common.Exists(sdk.Path) {
204                         return &s, fmt.Errorf(errMsg + "(path not accessible)")
205                 }
206         }
207
208         // Use V3 to ensure that we get same uuid on restart
209         nm := s.sdk.Name
210         if nm == "" {
211                 nm = s.sdk.Profile + "_" + s.sdk.Arch + "_" + s.sdk.Version
212         }
213         s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), nm).String()
214
215         s.LogSillyf("New SDK: ID=%v, Family=%s, Name=%v", s.sdk.ID[:8], s.sdk.FamilyConf.FamilyName, s.sdk.Name)
216
217         return &s, nil
218 }
219
220 // Install a SDK (non blocking command, IOW run in background)
221 func (s *CrossSDK) Install(file string, force bool, timeout int, args []string, sess *ClientSession) error {
222
223         if s.sdk.Status == xsapiv1.SdkStatusInstalled && !force {
224                 return fmt.Errorf("already installed")
225         }
226         if s.sdk.Status == xsapiv1.SdkStatusInstalling {
227                 return fmt.Errorf("installation in progress")
228         }
229
230         // Compute command args
231         cmdArgs := []string{}
232         if file != "" {
233                 cmdArgs = append(cmdArgs, "--file", file)
234         } else {
235                 cmdArgs = append(cmdArgs, "--url", s.sdk.URL)
236         }
237         if force {
238                 cmdArgs = append(cmdArgs, "--force")
239         }
240
241         // Append additional args (passthrough arguments)
242         if len(args) > 0 {
243                 cmdArgs = append(cmdArgs, args...)
244         }
245
246         // Unique command id
247         sdkCmdID++
248         cmdID := "sdk-install-" + strconv.Itoa(sdkCmdID)
249
250         // Create new instance to execute command and sent output over WS
251         s.installCmd = eows.New(s.scripts[scriptAdd], cmdArgs, sess.IOSocket, sess.ID, cmdID)
252         s.installCmd.Log = s.Log
253         // TODO: enable Term s.installCmd.PtyMode = true
254         s.installCmd.LineTimeSpan = 500 * time.Millisecond.Nanoseconds()
255         if timeout > 0 {
256                 s.installCmd.CmdExecTimeout = timeout
257         } else {
258                 s.installCmd.CmdExecTimeout = 30 * 60 // default 30min
259         }
260
261         // Define callback for output (stdout+stderr)
262         s.installCmd.OutputCB = func(e *eows.ExecOverWS, bStdout, bStderr []byte) {
263
264                 stdout := string(bStdout)
265                 stderr := string(bStderr)
266
267                 // paranoia
268                 data := e.UserData
269                 sdkID := (*data)["SDKID"].(string)
270                 if sdkID != s.sdk.ID {
271                         s.Log.Errorf("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
272                 }
273
274                 // IO socket can be nil when disconnected
275                 so := s.sessions.IOSocketGet(e.Sid)
276                 if so == nil {
277                         s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.EVTSDKManagement, e.Sid, e.CmdID)
278                         return
279                 }
280
281                 if s.LogLevelSilly {
282                         s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - SDK ID:%s:", xsapiv1.EVTSDKManagement, e.Sid[4:], e.CmdID, sdkID[:16])
283                         if stdout != "" {
284                                 s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
285                         }
286                         if stderr != "" {
287                                 s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
288                         }
289                 }
290
291                 err := (*so).Emit(xsapiv1.EVTSDKManagement, xsapiv1.SDKManagementMsg{
292                         CmdID:     e.CmdID,
293                         Timestamp: time.Now().String(),
294                         Action:    xsapiv1.SdkMgtActionInstall,
295                         Sdk:       s.sdk,
296                         Progress:  0, // TODO add progress
297                         Exited:    false,
298                         Stdout:    stdout,
299                         Stderr:    stderr,
300                 })
301                 if err != nil {
302                         s.Log.Errorf("WS Emit : %v", err)
303                 }
304         }
305
306         // Define callback for output
307         s.installCmd.ExitCB = func(e *eows.ExecOverWS, code int, exitError error) {
308                 defer LockXdsUpdateCounter(s.Context, false)
309
310                 // paranoia
311                 data := e.UserData
312                 sdkID := (*data)["SDKID"].(string)
313                 if sdkID != s.sdk.ID {
314                         s.Log.Errorf("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
315                 }
316
317                 s.Log.Infof("Command SDK ID %s [Cmd ID %s]  exited: code %d, exitError: %v", sdkID[:16], e.CmdID, code, exitError)
318
319                 // IO socket can be nil when disconnected
320                 so := s.sessions.IOSocketGet(e.Sid)
321                 if so == nil {
322                         s.Log.Infof("%s (exit) not emitted - WS closed (id:%s)", xsapiv1.EVTSDKManagement, e.CmdID)
323                         return
324                 }
325
326                 // Update SDK status
327                 if code == 0 && exitError == nil {
328                         s.sdk.LastError = ""
329                         s.sdk.Status = xsapiv1.SdkStatusInstalled
330
331                         // FIXME: better update it using monitoring install dir (inotify)
332                         // (see sdks.go / monitorSDKInstallation )
333                         // Update SetupFile when n
334                         if s.sdk.SetupFile == "" {
335                                 sdkDef, err := GetSDKInfo(s.sdk.FamilyConf.ScriptsDir, s.sdk.URL, "", "", s.sdk.UUID, s.Log)
336                                 if err != nil || sdkDef.SetupFile == "" {
337                                         s.Log.Errorf("GetSDKInfo error: %v", err)
338                                         code = 1
339                                         s.sdk.LastError = "Installation failed (cannot init SetupFile path)"
340                                         s.sdk.Status = xsapiv1.SdkStatusNotInstalled
341                                 } else {
342                                         s.sdk.SetupFile = sdkDef.SetupFile
343                                 }
344                         }
345
346                 } else {
347                         s.sdk.LastError = "Installation failed (code " + strconv.Itoa(code) +
348                                 ")"
349                         if exitError != nil {
350                                 s.sdk.LastError = ". Error: " + exitError.Error()
351                         }
352                         s.sdk.Status = xsapiv1.SdkStatusNotInstalled
353                 }
354
355                 emitErr := ""
356                 if exitError != nil {
357                         emitErr = exitError.Error()
358                 }
359                 if emitErr == "" && s.sdk.LastError != "" {
360                         emitErr = s.sdk.LastError
361                 }
362
363                 // Emit event
364                 errSoEmit := (*so).Emit(xsapiv1.EVTSDKManagement, xsapiv1.SDKManagementMsg{
365                         CmdID:     e.CmdID,
366                         Timestamp: time.Now().String(),
367                         Action:    xsapiv1.SdkMgtActionInstall,
368                         Sdk:       s.sdk,
369                         Progress:  100,
370                         Exited:    true,
371                         Code:      code,
372                         Error:     emitErr,
373                 })
374                 if errSoEmit != nil {
375                         s.Log.Errorf("WS Emit EVTSDKManagement : %v", errSoEmit)
376                 }
377
378                 errSoEmit = s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, e.Sid)
379                 if errSoEmit != nil {
380                         s.Log.Errorf("WS Emit EVTSDKStateChange : %v", errSoEmit)
381                 }
382
383                 // Cleanup command for the next time
384                 s.installCmd = nil
385         }
386
387         // User data (used within callbacks)
388         data := make(map[string]interface{})
389         data["SDKID"] = s.sdk.ID
390         s.installCmd.UserData = &data
391
392         // Start command execution
393         s.Log.Infof("Install SDK %s: cmdID=%v, cmd=%v, args=%v", s.sdk.Name, s.installCmd.CmdID, s.installCmd.Cmd, s.installCmd.Args)
394
395         s.sdk.Status = xsapiv1.SdkStatusInstalling
396         s.sdk.LastError = ""
397
398         if err := s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, sess.ID); err != nil {
399                 s.Log.Errorf("WS Emit EVTSDKStateChange installing : %v", err)
400         }
401
402         err := s.installCmd.Start()
403
404         return err
405 }
406
407 // AbortInstallRemove abort an install or remove command
408 func (s *CrossSDK) AbortInstallRemove(timeout int) error {
409
410         if s.installCmd == nil {
411                 return fmt.Errorf("no installation in progress for this sdk")
412         }
413
414         s.sdk.Status = xsapiv1.SdkStatusNotInstalled
415         return s.installCmd.Signal("SIGKILL")
416 }
417
418 // Remove Used to remove/uninstall a SDK
419 func (s *CrossSDK) Remove(timeout int, sess *ClientSession) error {
420
421         if s.sdk.Status != xsapiv1.SdkStatusInstalled {
422                 return fmt.Errorf("this sdk is not installed")
423         }
424
425         // IO socket can be nil when disconnected
426         so := s.sessions.IOSocketGet(sess.ID)
427         if so == nil {
428                 return fmt.Errorf("Cannot retrieve socket ")
429         }
430
431         s.sdk.Status = xsapiv1.SdkStatusUninstalling
432
433         // Notify state change
434         if err := s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, sess.ID); err != nil {
435                 s.Log.Warningf("Cannot notify SDK remove: %v", err)
436         }
437
438         script := s.scripts[scriptRemove]
439         args := s.sdk.Path
440         s.Log.Infof("Uninstall SDK %s: script=%v args=%v", s.sdk.Name, script, args)
441
442         // Notify start removing
443         evData := xsapiv1.SDKManagementMsg{
444                 Timestamp: time.Now().String(),
445                 Action:    xsapiv1.SdkMgtActionRemove,
446                 Sdk:       s.sdk,
447                 Progress:  0,
448                 Exited:    false,
449                 Code:      0,
450                 Error:     "",
451         }
452         if errEmit := (*so).Emit(xsapiv1.EVTSDKManagement, evData); errEmit != nil {
453                 s.Log.Warningf("Cannot notify EVTSDKManagement end: %v", errEmit)
454         }
455
456         // Run command to remove SDK
457         cmd := exec.Command(script, args)
458         stdout, err := cmd.CombinedOutput()
459
460         s.sdk.Status = xsapiv1.SdkStatusNotInstalled
461         s.Log.Debugf("SDK uninstall err %v, output:\n %v", err, string(stdout))
462
463         // Emit end of removing process
464         evData = xsapiv1.SDKManagementMsg{
465                 Timestamp: time.Now().String(),
466                 Action:    xsapiv1.SdkMgtActionRemove,
467                 Sdk:       s.sdk,
468                 Progress:  100,
469                 Exited:    true,
470                 Code:      0,
471                 Error:     "",
472         }
473
474         // Update error code on error
475         if err != nil {
476                 evData.Code = 1
477                 evData.Error = err.Error()
478         }
479
480         if errEmit := (*so).Emit(xsapiv1.EVTSDKManagement, evData); errEmit != nil {
481                 s.Log.Warningf("Cannot notify EVTSDKManagement end: %v", errEmit)
482         }
483
484         // Notify state change
485         if errEmit := s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, sess.ID); errEmit != nil {
486                 s.Log.Warningf("Cannot notify EVTSDKStateChange end: %v", errEmit)
487         }
488
489         if err != nil {
490                 return fmt.Errorf("Error while uninstalling sdk: %v", err)
491         }
492         return nil
493 }
494
495 // Get Return SDK definition
496 func (s *CrossSDK) Get() *xsapiv1.SDK {
497         return &s.sdk
498 }
499
500 // GetEnvCmd returns the command used to initialized the environment
501 func (s *CrossSDK) GetEnvCmd() []string {
502         if s.sdk.SetupFile == "" {
503                 return []string{}
504         }
505         return []string{"source", s.sdk.SetupFile}
506
507 }