2 * Copyright (C) 2017-2018 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
29 common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
30 "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib/eows"
31 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
32 "github.com/Sirupsen/logrus"
33 uuid "github.com/satori/go.uuid"
36 // Definition of scripts used to managed SDKs
39 scriptDbDump = "db-dump"
40 scriptDbUpdate = "db-update"
41 scriptGetFamConfig = "get-family-config"
42 scriptGetSdkInfo = "get-sdk-info"
43 scriptRemove = "remove"
46 var scriptsAll = []string{
57 // CrossSDK Hold SDK config
58 type CrossSDK struct {
61 scripts map[string]string
62 installCmd *eows.ExecOverWS
63 removeCmd *eows.ExecOverWS
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{}
70 // First update sdk DB when requested
72 out, err := UpdateSDKDb(scriptDir, log)
74 log.Errorf("SDK DB update failure (%v): %v", err, out)
75 return sdksList, fmt.Errorf("Error while updating SDK DB (%v)", err)
79 // Retrieve SDKs list and info
80 cmd := exec.Command(path.Join(scriptDir, scriptDbDump))
81 stdout, err := cmd.CombinedOutput()
83 return sdksList, fmt.Errorf("Cannot get sdks list: %v", err)
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)
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) {
100 args = append(args, "--url", url)
101 } else if filename != "" {
102 args = append(args, "--file", filename)
104 args = append(args, "--md5", md5sum)
107 return sdk, fmt.Errorf("url of filename must be set")
110 args = append(args, "--uuid", uuid)
113 cmd := exec.Command(path.Join(scriptDir, scriptGetSdkInfo), args...)
114 stdout, err := cmd.CombinedOutput()
116 return sdk, fmt.Errorf("%v %v", string(stdout), err)
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)
126 // UpdateSDKDb Used db-update script to update SDK database
127 func UpdateSDKDb(scriptDir string, log *logrus.Logger) (string, error) {
129 cmd := exec.Command(path.Join(scriptDir, scriptDbUpdate), args...)
130 stdout, err := cmd.CombinedOutput()
132 return string(stdout), err
135 // NewCrossSDK creates a new instance of CrossSDK
136 func NewCrossSDK(ctx *Context, sdk xsapiv1.SDK, scriptDir string) (*CrossSDK, error) {
140 scripts: make(map[string]string),
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)
149 cmd := exec.Command(getConfFile)
150 stdout, err := cmd.CombinedOutput()
152 return &s, fmt.Errorf("Cannot get sdk config using %s: %v", getConfFile, err)
155 err = json.Unmarshal(stdout, &s.sdk.FamilyConf)
157 s.Log.Errorf("SDK config script output:\n%v\n", string(stdout))
158 return &s, fmt.Errorf("Cannot decode sdk config %v", err)
160 famName := s.sdk.FamilyConf.FamilyName
163 if s.sdk.FamilyConf.RootDir == "" {
164 return &s, fmt.Errorf("SDK config not valid (rootDir not set)")
166 if s.sdk.FamilyConf.EnvSetupFile == "" {
167 return &s, fmt.Errorf("SDK config not valid (envSetupFile not set)")
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)
178 // Fixed default fields value
180 if sdk.Status == "" {
181 sdk.Status = xsapiv1.SdkStatusNotInstalled
185 errMsg := "Invalid SDK definition "
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)")
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)")
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)")
208 // Use V3 to ensure that we get same uuid on restart
211 nm = s.sdk.Profile + "_" + s.sdk.Arch + "_" + s.sdk.Version
213 s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), nm).String()
215 s.LogSillyf("New SDK: ID=%v, Family=%s, Name=%v", s.sdk.ID[:8], s.sdk.FamilyConf.FamilyName, s.sdk.Name)
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 {
223 if s.sdk.Status == xsapiv1.SdkStatusInstalled && !force {
224 return fmt.Errorf("already installed")
226 if s.sdk.Status == xsapiv1.SdkStatusInstalling {
227 return fmt.Errorf("installation in progress")
230 // Compute command args
231 cmdArgs := []string{}
233 cmdArgs = append(cmdArgs, "--file", file)
235 cmdArgs = append(cmdArgs, "--url", s.sdk.URL)
238 cmdArgs = append(cmdArgs, "--force")
241 // Append additional args (passthrough arguments)
243 cmdArgs = append(cmdArgs, args...)
248 cmdID := "sdk-install-" + strconv.Itoa(sdkCmdID)
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()
256 s.installCmd.CmdExecTimeout = timeout
258 s.installCmd.CmdExecTimeout = 30 * 60 // default 30min
261 // Define callback for output (stdout+stderr)
262 s.installCmd.OutputCB = func(e *eows.ExecOverWS, bStdout, bStderr []byte) {
264 stdout := string(bStdout)
265 stderr := string(bStderr)
269 sdkID := (*data)["SDKID"].(string)
270 if sdkID != s.sdk.ID {
271 s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
274 // IO socket can be nil when disconnected
275 so := s.sessions.IOSocketGet(e.Sid)
277 s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.EVTSDKManagement, e.Sid, e.CmdID)
282 s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - SDK ID:%s:", xsapiv1.EVTSDKManagement, e.Sid[4:], e.CmdID, sdkID[:16])
284 s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
287 s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
291 err := (*so).Emit(xsapiv1.EVTSDKManagement, xsapiv1.SDKManagementMsg{
293 Timestamp: time.Now().String(),
294 Action: xsapiv1.SdkMgtActionInstall,
296 Progress: 0, // TODO add progress
302 s.Log.Errorf("WS Emit : %v", err)
306 // Define callback for output
307 s.installCmd.ExitCB = func(e *eows.ExecOverWS, code int, exitError error) {
310 sdkID := (*data)["SDKID"].(string)
311 if sdkID != s.sdk.ID {
312 s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
315 s.Log.Infof("Command SDK ID %s [Cmd ID %s] exited: code %d, exitError: %v", sdkID[:16], e.CmdID, code, exitError)
317 // IO socket can be nil when disconnected
318 so := s.sessions.IOSocketGet(e.Sid)
320 s.Log.Infof("%s (exit) not emitted - WS closed (id:%s)", xsapiv1.EVTSDKManagement, e.CmdID)
325 if code == 0 && exitError == nil {
327 s.sdk.Status = xsapiv1.SdkStatusInstalled
329 // FIXME: better update it using monitoring install dir (inotify)
330 // (see sdks.go / monitorSDKInstallation )
331 // Update SetupFile when n
332 if s.sdk.SetupFile == "" {
333 sdkDef, err := GetSDKInfo(s.sdk.FamilyConf.ScriptsDir, s.sdk.URL, "", "", s.sdk.UUID, s.Log)
334 if err != nil || sdkDef.SetupFile == "" {
335 s.Log.Errorf("GetSDKInfo error: %v", err)
337 s.sdk.LastError = "Installation failed (cannot init SetupFile path)"
338 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
340 s.sdk.SetupFile = sdkDef.SetupFile
345 s.sdk.LastError = "Installation failed (code " + strconv.Itoa(code) +
347 if exitError != nil {
348 s.sdk.LastError = ". Error: " + exitError.Error()
350 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
354 if exitError != nil {
355 emitErr = exitError.Error()
357 if emitErr == "" && s.sdk.LastError != "" {
358 emitErr = s.sdk.LastError
362 errSoEmit := (*so).Emit(xsapiv1.EVTSDKManagement, xsapiv1.SDKManagementMsg{
364 Timestamp: time.Now().String(),
365 Action: xsapiv1.SdkMgtActionInstall,
372 if errSoEmit != nil {
373 s.Log.Errorf("WS Emit EVTSDKManagement : %v", errSoEmit)
376 errSoEmit = s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, e.Sid)
377 if errSoEmit != nil {
378 s.Log.Errorf("WS Emit EVTSDKStateChange : %v", errSoEmit)
381 // Cleanup command for the next time
385 // User data (used within callbacks)
386 data := make(map[string]interface{})
387 data["SDKID"] = s.sdk.ID
388 s.installCmd.UserData = &data
390 // Start command execution
391 s.Log.Infof("Install SDK %s: cmdID=%v, cmd=%v, args=%v", s.sdk.Name, s.installCmd.CmdID, s.installCmd.Cmd, s.installCmd.Args)
393 s.sdk.Status = xsapiv1.SdkStatusInstalling
396 if err := s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, sess.ID); err != nil {
397 s.Log.Errorf("WS Emit EVTSDKStateChange installing : %v", err)
400 err := s.installCmd.Start()
405 // AbortInstallRemove abort an install or remove command
406 func (s *CrossSDK) AbortInstallRemove(timeout int) error {
408 if s.installCmd == nil {
409 return fmt.Errorf("no installation in progress for this sdk")
412 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
413 return s.installCmd.Signal("SIGKILL")
416 // Remove Used to remove/uninstall a SDK
417 func (s *CrossSDK) Remove(timeout int, sess *ClientSession) error {
419 if s.sdk.Status != xsapiv1.SdkStatusInstalled {
420 return fmt.Errorf("this sdk is not installed")
423 // IO socket can be nil when disconnected
424 so := s.sessions.IOSocketGet(sess.ID)
426 return fmt.Errorf("Cannot retrieve socket ")
429 s.sdk.Status = xsapiv1.SdkStatusUninstalling
431 // Notify state change
432 if err := s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, sess.ID); err != nil {
433 s.Log.Warningf("Cannot notify SDK remove: %v", err)
436 script := s.scripts[scriptRemove]
438 s.Log.Infof("Uninstall SDK %s: script=%v args=%v", s.sdk.Name, script, args)
440 // Notify start removing
441 evData := xsapiv1.SDKManagementMsg{
442 Timestamp: time.Now().String(),
443 Action: xsapiv1.SdkMgtActionRemove,
450 if errEmit := (*so).Emit(xsapiv1.EVTSDKManagement, evData); errEmit != nil {
451 s.Log.Warningf("Cannot notify EVTSDKManagement end: %v", errEmit)
454 // Run command to remove SDK
455 cmd := exec.Command(script, args)
456 stdout, err := cmd.CombinedOutput()
458 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
459 s.Log.Debugf("SDK uninstall err %v, output:\n %v", err, string(stdout))
461 // Emit end of removing process
462 evData = xsapiv1.SDKManagementMsg{
463 Timestamp: time.Now().String(),
464 Action: xsapiv1.SdkMgtActionRemove,
472 // Update error code on error
475 evData.Error = err.Error()
478 if errEmit := (*so).Emit(xsapiv1.EVTSDKManagement, evData); errEmit != nil {
479 s.Log.Warningf("Cannot notify EVTSDKManagement end: %v", errEmit)
482 // Notify state change
483 if errEmit := s.events.Emit(xsapiv1.EVTSDKStateChange, s.sdk, sess.ID); errEmit != nil {
484 s.Log.Warningf("Cannot notify EVTSDKStateChange end: %v", errEmit)
488 return fmt.Errorf("Error while uninstalling sdk: %v", err)
493 // Get Return SDK definition
494 func (s *CrossSDK) Get() *xsapiv1.SDK {
498 // GetEnvCmd returns the command used to initialized the environment
499 func (s *CrossSDK) GetEnvCmd() []string {
500 if s.sdk.SetupFile == "" {
503 return []string{"source", s.sdk.SetupFile}