Fixed SDKs management when running in xds VM.
[src/xds/xds-server.git] / lib / xdsserver / sdks.go
1 /*
2  * Copyright (C) 2017 "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         "fmt"
22         "path"
23         "path/filepath"
24         "strings"
25         "sync"
26
27         common "github.com/iotbzh/xds-common/golib"
28         "github.com/iotbzh/xds-server/lib/xsapiv1"
29         uuid "github.com/satori/go.uuid"
30 )
31
32 // SDKs List of installed SDK
33 type SDKs struct {
34         *Context
35         Sdks map[string]*CrossSDK
36
37         mutex sync.Mutex
38         stop  chan struct{} // signals intentional stop
39 }
40
41 // NewSDKs creates a new instance of SDKs
42 func NewSDKs(ctx *Context) (*SDKs, error) {
43         s := SDKs{
44                 Context: ctx,
45                 Sdks:    make(map[string]*CrossSDK),
46                 stop:    make(chan struct{}),
47         }
48
49         scriptsDir := ctx.Config.FileConf.SdkScriptsDir
50         if !common.Exists(scriptsDir) {
51                 // allow to use scripts/sdk in debug mode
52                 scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.SdkScriptsDir), "scripts", "sdks")
53                 if !common.Exists(scriptsDir) {
54                         return &s, fmt.Errorf("scripts directory doesn't exist (%v)", scriptsDir)
55                 }
56         }
57         s.Log.Infof("SDK scripts dir: %s", scriptsDir)
58
59         dirs, err := filepath.Glob(path.Join(scriptsDir, "*"))
60         if err != nil {
61                 s.Log.Errorf("Error while retrieving SDK scripts: dir=%s, error=%s", scriptsDir, err.Error())
62                 return &s, err
63         }
64
65         s.mutex.Lock()
66         defer s.mutex.Unlock()
67
68         // Foreach directories in scripts/sdk
69         nbInstalled := 0
70         monSdksPath := make(map[string]*xsapiv1.SDKFamilyConfig)
71         for _, d := range dirs {
72                 if !common.IsDir(d) {
73                         continue
74                 }
75
76                 sdksList, err := ListCrossSDK(d, s.Log)
77                 if err != nil {
78                         // allow to use XDS even if error on list
79                         s.Log.Errorf("Cannot retrieve SDK list: %v", err)
80                 }
81                 s.LogSillyf("'%s' SDKs list: %v", d, sdksList)
82
83                 for _, sdk := range sdksList {
84                         cSdk, err := NewCrossSDK(ctx, sdk, d)
85                         if err != nil {
86                                 s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error())
87                                 continue
88                         }
89                         if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
90                                 s.Log.Warningf("Duplicate SDK ID : %v", cSdk.sdk.ID)
91                                 cSdk.sdk.ID += "_DUPLICATE_" + uuid.NewV1().String()
92                         }
93                         s.Sdks[cSdk.sdk.ID] = cSdk
94                         if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
95                                 nbInstalled++
96                         }
97
98                         monSdksPath[cSdk.sdk.FamilyConf.RootDir] = &cSdk.sdk.FamilyConf
99                 }
100         }
101
102         ctx.Log.Debugf("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
103
104         // Start monitor thread to detect new SDKs
105         if len(monSdksPath) == 0 {
106                 s.Log.Warningf("No cross SDKs definition found")
107         }
108
109         return &s, nil
110 }
111
112 // Stop SDKs management
113 func (s *SDKs) Stop() {
114         close(s.stop)
115 }
116
117 // monitorSDKInstallation
118 /* TODO: cleanup
119 func (s *SDKs) monitorSDKInstallation(monSDKs map[string]*xsapiv1.SDKFamilyConfig) {
120
121         // Set up a watchpoint listening for inotify-specific events
122         c := make(chan notify.EventInfo, 1)
123
124         addWatcher := func(rootDir string) error {
125                 s.Log.Debugf("SDK Register watcher: rootDir=%s", rootDir)
126
127                 if err := notify.Watch(rootDir+"/...", c, notify.Create, notify.Remove); err != nil {
128                         return fmt.Errorf("SDK monitor: rootDir=%v err=%v", rootDir, err)
129                 }
130                 return nil
131         }
132
133         // Add directory watchers
134         for dir := range monSDKs {
135                 if err := addWatcher(dir); err != nil {
136                         s.Log.Errorln(err.Error())
137                 }
138         }
139
140         // Wait inotify or stop events
141         for {
142                 select {
143                 case <-s.stop:
144                         s.Log.Debugln("Stop monitorSDKInstallation")
145                         notify.Stop(c)
146                         return
147                 case ei := <-c:
148                         s.LogSillyf("monitorSDKInstallation SDKs event %v, path %v\n", ei.Event(), ei.Path())
149
150                         // Filter out all event that doesn't match environment file
151                         if !strings.Contains(ei.Path(), "environment-setup-") {
152                                 continue
153                         }
154                         dir := path.Dir(ei.Path())
155
156                         sdk, err := s.GetByPath(dir)
157                         if err != nil {
158                                 s.Log.Warningf("Cannot find SDK path to notify creation")
159                                 s.LogSillyf("event: %v", ei.Event())
160                                 continue
161                         }
162
163                         switch ei.Event() {
164                         case notify.Create:
165                                 // Emit Folder state change event
166                                 if err := s.events.Emit(xsapiv1.EVTSDKInstall, sdk, ""); err != nil {
167                                         s.Log.Warningf("Cannot notify SDK install: %v", err)
168                                 }
169
170                         case notify.Remove, notify.InMovedFrom:
171                                 // Emit Folder state change event
172                                 if err := s.events.Emit(xsapiv1.EVTSDKRemove, sdk, ""); err != nil {
173                                         s.Log.Warningf("Cannot notify SDK remove: %v", err)
174                                 }
175                         }
176                 }
177         }
178 }
179 */
180
181 // ResolveID Complete an SDK ID (helper for user that can use partial ID value)
182 func (s *SDKs) ResolveID(id string) (string, error) {
183         if id == "" {
184                 return "", nil
185         }
186
187         match := []string{}
188         for iid := range s.Sdks {
189                 if strings.HasPrefix(iid, id) {
190                         match = append(match, iid)
191                 }
192         }
193
194         if len(match) == 1 {
195                 return match[0], nil
196         } else if len(match) == 0 {
197                 return id, fmt.Errorf("Unknown sdk id")
198         }
199         return id, fmt.Errorf("Multiple sdk IDs found with provided prefix: " + id)
200 }
201
202 // Get returns an SDK from id
203 func (s *SDKs) Get(id string) *xsapiv1.SDK {
204         s.mutex.Lock()
205         defer s.mutex.Unlock()
206
207         sc, exist := s.Sdks[id]
208         if !exist {
209                 return nil
210         }
211         return (*sc).Get()
212 }
213
214 // GetByPath Find a SDK from path
215 func (s *SDKs) GetByPath(path string) (*xsapiv1.SDK, error) {
216         if path == "" {
217                 return nil, fmt.Errorf("can't found sdk (empty path)")
218         }
219         for _, ss := range s.Sdks {
220                 if ss.sdk.Path == path {
221                         return ss.Get(), nil
222                 }
223         }
224         return nil, fmt.Errorf("not found")
225 }
226
227 // GetAll returns all existing SDKs
228 func (s *SDKs) GetAll() []xsapiv1.SDK {
229         s.mutex.Lock()
230         defer s.mutex.Unlock()
231         res := []xsapiv1.SDK{}
232         for _, v := range s.Sdks {
233                 res = append(res, *(*v).Get())
234         }
235         return res
236 }
237
238 // GetEnvCmd returns the command used to initialized the environment for an SDK
239 func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
240         if id == "" && defaultID == "" {
241                 // no env cmd
242                 return []string{}
243         }
244
245         s.mutex.Lock()
246         defer s.mutex.Unlock()
247
248         if iid, err := s.ResolveID(id); err == nil {
249                 if sdk, exist := s.Sdks[iid]; exist {
250                         return sdk.GetEnvCmd()
251                 }
252         }
253
254         if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist {
255                 return sdk.GetEnvCmd()
256         }
257
258         // Return default env that may be empty
259         return []string{}
260 }
261
262 // Install Used to install a new SDK
263 func (s *SDKs) Install(id, filepath string, force bool, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
264         var cSdk *CrossSDK
265         if id != "" && filepath != "" {
266                 return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
267         }
268         if id != "" {
269                 var exist bool
270                 cSdk, exist = s.Sdks[id]
271                 if !exist {
272                         return nil, fmt.Errorf("unknown id")
273                 }
274         } else if filepath != "" {
275                 // TODO check that file is accessible
276
277         } else {
278                 return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
279         }
280
281         s.mutex.Lock()
282         defer s.mutex.Unlock()
283
284         // Launch script to install
285         // (note that add event will be generated by monitoring thread)
286         if err := cSdk.Install(filepath, force, timeout, sess); err != nil {
287                 return &cSdk.sdk, err
288         }
289
290         return &cSdk.sdk, nil
291 }
292
293 // AbortInstall Used to abort SDK installation
294 func (s *SDKs) AbortInstall(id string, timeout int) (*xsapiv1.SDK, error) {
295
296         if id == "" {
297                 return nil, fmt.Errorf("invalid parameter")
298         }
299         cSdk, exist := s.Sdks[id]
300         if !exist {
301                 return nil, fmt.Errorf("unknown id")
302         }
303
304         s.mutex.Lock()
305         defer s.mutex.Unlock()
306
307         err := cSdk.AbortInstallRemove(timeout)
308
309         return &cSdk.sdk, err
310 }
311
312 // Remove Used to uninstall a SDK
313 func (s *SDKs) Remove(id string, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
314
315         cSdk, exist := s.Sdks[id]
316         if !exist {
317                 return nil, fmt.Errorf("unknown id")
318         }
319
320         s.mutex.Lock()
321         defer s.mutex.Unlock()
322
323         // Launch script to remove/uninstall
324         // (note that remove event will be generated by monitoring thread)
325         if err := cSdk.Remove(timeout, sess); err != nil {
326                 return &cSdk.sdk, err
327         }
328
329         sdk := cSdk.sdk
330
331         // Don't delete it from s.Sdks
332         // (always keep sdk reference to allow for example re-install)
333
334         return &sdk, nil
335 }