Added target and terminal support.
[src/xds/xds-server.git] / lib / xdsserver / targets.go
1 /*
2  * Copyright (C) 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/xml"
22         "fmt"
23         "log"
24         "os"
25         "strings"
26
27         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
28         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig"
29         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
30         socketio "github.com/googollee/go-socket.io"
31         "github.com/syncthing/syncthing/lib/sync"
32 )
33
34 // Targets Represent a XDS targets
35 type Targets struct {
36         *Context
37         fileOnDisk string
38         tgts       map[string]*ITARGET
39         terminals  map[string]*Terminals
40 }
41
42 // Mutex to make add/delete atomic
43 var tcMutex = sync.NewMutex()
44
45 /***
46  * Targets
47  ***/
48
49 // TargetsConstructor Create a new instance of Model Target
50 func TargetsConstructor(ctx *Context) *Targets {
51         file, _ := xdsconfig.TargetsConfigFilenameGet()
52         return &Targets{
53                 Context:    ctx,
54                 fileOnDisk: file,
55                 tgts:       make(map[string]*ITARGET),
56                 terminals:  make(map[string]*Terminals),
57         }
58 }
59
60 // LoadConfig Load targets configuration from disk
61 func (t *Targets) LoadConfig() error {
62         var tgts []xsapiv1.TargetConfig
63
64         if t.fileOnDisk != "" {
65                 t.Log.Infof("Use target config file: %s", t.fileOnDisk)
66                 err := targetsConfigRead(t.fileOnDisk, &tgts)
67                 if err != nil {
68                         if strings.HasPrefix(err.Error(), "EOF") {
69                                 t.Log.Warnf("Empty target config file")
70                         } else if strings.HasPrefix(err.Error(), "No target config") {
71                                 t.Log.Warnf(err.Error())
72                         } else {
73                                 return err
74                         }
75                 }
76         } else {
77                 t.Log.Warnf("Targets config filename not set")
78         }
79
80         // Update targets
81         t.Log.Infof("Loading initial targets config: %d targets found", len(tgts))
82         for _, tc := range tgts {
83                 if _, err := t.createUpdate(tc, false, true); err != nil {
84                         return err
85                 }
86         }
87
88         return nil
89 }
90
91 // SaveConfig Save targets configuration to disk
92 func (t *Targets) SaveConfig() error {
93         if t.fileOnDisk == "" {
94                 return fmt.Errorf("Targets config filename not set")
95         }
96
97         // FIXME: buffered save or avoid to write on disk each time
98         return targetsConfigWrite(t.fileOnDisk, t.getConfigArrUnsafe())
99 }
100
101 // ResolveID Complete a Target ID (helper for user that can use partial ID value)
102 func (t *Targets) ResolveID(id string) (string, error) {
103         if id == "" {
104                 return "", nil
105         }
106
107         match := []string{}
108         for iid := range t.tgts {
109                 if strings.HasPrefix(iid, id) {
110                         match = append(match, iid)
111                 }
112         }
113
114         if len(match) == 1 {
115                 return match[0], nil
116         } else if len(match) == 0 {
117                 return id, fmt.Errorf("Unknown id")
118         }
119         return id, fmt.Errorf("Multiple IDs found %v", match)
120 }
121
122 // Get returns the target config or nil if not existing
123 func (t *Targets) Get(id string) *ITARGET {
124         if id == "" {
125                 return nil
126         }
127         tc, exist := t.tgts[id]
128         if !exist {
129                 return nil
130         }
131         return tc
132 }
133
134 // GetConfigArr returns the config of all targets as an array
135 func (t *Targets) GetConfigArr() []xsapiv1.TargetConfig {
136         tcMutex.Lock()
137         defer tcMutex.Unlock()
138
139         return t.getConfigArrUnsafe()
140 }
141
142 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
143 func (t *Targets) getConfigArrUnsafe() []xsapiv1.TargetConfig {
144         conf := []xsapiv1.TargetConfig{}
145         for _, v := range t.tgts {
146                 conf = append(conf, (*v).GetConfig())
147         }
148         return conf
149 }
150
151 // Add adds a new target
152 func (t *Targets) Add(newT xsapiv1.TargetConfig) (*xsapiv1.TargetConfig, error) {
153         return t.createUpdate(newT, true, false)
154 }
155
156 // CreateUpdate creates or update a target
157 func (t *Targets) createUpdate(newT xsapiv1.TargetConfig, create bool, initial bool) (*xsapiv1.TargetConfig, error) {
158         var err error
159
160         tcMutex.Lock()
161         defer tcMutex.Unlock()
162
163         // Sanity check
164         if _, exist := t.tgts[newT.ID]; exist {
165                 return nil, fmt.Errorf("ID already exists")
166         }
167
168         var tgt ITARGET
169         switch newT.Type {
170         case xsapiv1.TypeTgtStandard:
171                 tgt = NewTargetStandard(t.Context)
172         default:
173                 return nil, fmt.Errorf("Unsupported target type")
174         }
175
176         // Allocate a new UUID
177         if create {
178                 newT.ID = tgt.NewUID("")
179         }
180         if !create && newT.ID == "" {
181                 return nil, fmt.Errorf("Cannot update target with null ID")
182         }
183
184         if newT.Name == "" {
185                 newT.Name = "Target"
186                 if len(newT.ID) > 8 {
187                         newT.Name += "_" + newT.ID[0:8]
188                 } else {
189                         newT.Name += "_" + newT.ID
190                 }
191         }
192
193         // Call terminals constructor the first time
194         var terms *Terminals
195         if _, exist := t.terminals[newT.ID]; !exist {
196                 terms = TerminalsConstructor(t.Context)
197                 t.terminals[newT.ID] = terms
198         } else {
199                 terms = t.terminals[newT.ID]
200         }
201
202         var newTarget *xsapiv1.TargetConfig
203         if create {
204                 // Add target
205                 if newTarget, err = tgt.Add(newT, terms); err != nil {
206                         newT.Status = xsapiv1.StatusTgtErrorConfig
207                         log.Printf("ERROR Adding target: %v\n", err)
208                         return newTarget, err
209                 }
210         } else {
211                 // Just update target config
212                 if newTarget, err = tgt.Setup(newT, terms); err != nil {
213                         newT.Status = xsapiv1.StatusTgtErrorConfig
214                         log.Printf("ERROR Updating target: %v\n", err)
215                         return newTarget, err
216                 }
217         }
218
219         // Create terminals
220         for _, tc := range newT.Terms {
221                 _, err := t.CreateUpdateTerminal(newT.ID, tc, initial)
222                 if err != nil {
223                         return newTarget, err
224                 }
225         }
226
227         // Add to folders list
228         t.tgts[newT.ID] = &tgt
229
230         // Save config on disk
231         if !initial {
232                 if err := t.SaveConfig(); err != nil {
233                         return newTarget, err
234                 }
235         }
236
237         newTgt := tgt.GetConfig()
238         return &newTgt, nil
239 }
240
241 // Delete deletes a specific target
242 func (t *Targets) Delete(id string) (xsapiv1.TargetConfig, error) {
243         var err error
244
245         tcMutex.Lock()
246         defer tcMutex.Unlock()
247
248         tgc := xsapiv1.TargetConfig{}
249         tc, exist := t.tgts[id]
250         if !exist {
251                 return tgc, fmt.Errorf("unknown id")
252         }
253
254         tgc = (*tc).GetConfig()
255
256         if err = (*tc).Delete(); err != nil {
257                 return tgc, err
258         }
259
260         delete(t.tgts, id)
261
262         // Save config on disk
263         err = t.SaveConfig()
264
265         return tgc, err
266 }
267
268 /***
269  * Terminals
270  ***/
271
272 // GetTerminalsArr Return list of existing terminals
273 func (t *Targets) GetTerminalsArr(targetID string) ([]xsapiv1.TerminalConfig, error) {
274         arr := []xsapiv1.TerminalConfig{}
275
276         tm, exist := t.terminals[targetID]
277         if !exist {
278                 return arr, fmt.Errorf("unknown target id")
279         }
280
281         for _, tt := range (*tm).terms {
282                 arr = append(arr, (*tt).GetConfig())
283         }
284         return arr, nil
285 }
286
287 // GetTerminal Return info of a specific terminal
288 func (t *Targets) GetTerminal(targetID, termID string) (*ITERMINAL, error) {
289         tm, exist := t.terminals[targetID]
290         if !exist {
291                 return nil, fmt.Errorf("unknown target id")
292         }
293         term, exist := (*tm).terms[termID]
294         if !exist {
295                 return nil, fmt.Errorf("unknown terminal id")
296         }
297         return term, nil
298 }
299
300 // ResolveTerminalID Complete a Terminal ID (helper for user that can use partial ID value)
301 func (t *Targets) ResolveTerminalID(termID string) (string, error) {
302         if termID == "" {
303                 return "", fmt.Errorf("unknown terminal id")
304         }
305
306         match := []string{}
307         for _, tm := range t.terminals {
308                 for tid := range tm.terms {
309                         if strings.HasPrefix(tid, termID) {
310                                 match = append(match, tid)
311                         }
312                 }
313         }
314
315         if len(match) == 1 {
316                 return match[0], nil
317         } else if len(match) == 0 {
318                 return termID, fmt.Errorf("Unknown id")
319         }
320         return termID, fmt.Errorf("Multiple IDs found %v", match)
321 }
322
323 // CreateUpdateTerminal Create or Update a target terminal definition
324 func (t *Targets) CreateUpdateTerminal(targetID string, tmCfg xsapiv1.TerminalConfig, initial bool) (*xsapiv1.TerminalConfig, error) {
325
326         var term *xsapiv1.TerminalConfig
327
328         iTerm, err := t.GetTerminal(targetID, tmCfg.ID)
329         if err != nil && strings.Contains(err.Error(), "unknown target") {
330                 return nil, err
331         }
332
333         if iTerm != nil {
334                 // Update terminal config
335                 term = (*iTerm).UpdateConfig(tmCfg)
336         } else {
337                 // Auto create a new terminal when needed
338                 var err error
339                 if term, err = t.terminals[targetID].New(tmCfg, targetID); err != nil {
340                         return nil, err
341                 }
342         }
343
344         term.Status = xsapiv1.StatusTermEnable
345
346         // Save config on disk
347         if !initial {
348                 if err := t.SaveConfig(); err != nil {
349                         return term, err
350                 }
351         }
352
353         return term, nil
354 }
355
356 // DeleteTerminal Delete a target terminal definition
357 func (t *Targets) DeleteTerminal(targetID, termID string) (*xsapiv1.TerminalConfig, error) {
358         terms, exist := t.terminals[targetID]
359         if !exist {
360                 return nil, fmt.Errorf("unknown target id")
361         }
362
363         term, err := (*terms).Free(termID)
364         if err != nil {
365                 return term, err
366         }
367
368         // Save config on disk
369         if err := t.SaveConfig(); err != nil {
370                 return term, err
371         }
372
373         return term, nil
374 }
375
376 // OpenTerminal Open a target terminal
377 func (t *Targets) OpenTerminal(targetID, termID string, sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) {
378         terms, exist := t.terminals[targetID]
379         if !exist {
380                 return nil, fmt.Errorf("unknown target id")
381         }
382         return (*terms).Open(termID, sock, sessID)
383 }
384
385 // CloseTerminal Close a target terminal
386 func (t *Targets) CloseTerminal(targetID, termID string) (*xsapiv1.TerminalConfig, error) {
387         terms, exist := t.terminals[targetID]
388         if !exist {
389                 return nil, fmt.Errorf("unknown target id")
390         }
391         return (*terms).Close(termID)
392 }
393
394 // ResizeTerminal Set size (row+col) of a target terminal
395 func (t *Targets) ResizeTerminal(targetID, termID string, cols, rows uint16) (*xsapiv1.TerminalConfig, error) {
396         terms, exist := t.terminals[targetID]
397         if !exist {
398                 return nil, fmt.Errorf("unknown target id")
399         }
400         return (*terms).Resize(termID, cols, rows)
401 }
402
403 // SignalTerminal Send a signal to a target terminal
404 func (t *Targets) SignalTerminal(targetID, termID, sigNum string) error {
405         terms, exist := t.terminals[targetID]
406         if !exist {
407                 return fmt.Errorf("unknown target id")
408         }
409         return (*terms).Signal(termID, sigNum)
410 }
411
412 /**
413  * Private functions
414  **/
415
416 // Use XML format and not json to be able to save/load all fields including
417 // ones that are masked in json (IOW defined with `json:"-"`)
418 type xmlTargets struct {
419         XMLName xml.Name               `xml:"targets"`
420         Version string                 `xml:"version,attr"`
421         Targets []xsapiv1.TargetConfig `xml:"targets"`
422 }
423
424 // targetsConfigRead reads targets config from disk
425 func targetsConfigRead(file string, targets *[]xsapiv1.TargetConfig) error {
426         if !common.Exists(file) {
427                 return fmt.Errorf("No target config file found (%s)", file)
428         }
429
430         ffMutex.Lock()
431         defer ffMutex.Unlock()
432
433         fd, err := os.Open(file)
434         defer fd.Close()
435         if err != nil {
436                 return err
437         }
438
439         data := xmlTargets{}
440         err = xml.NewDecoder(fd).Decode(&data)
441         if err == nil {
442                 *targets = data.Targets
443         }
444         return err
445 }
446
447 // targetsConfigWrite writes targets config on disk
448 func targetsConfigWrite(file string, targets []xsapiv1.TargetConfig) error {
449         ffMutex.Lock()
450         defer ffMutex.Unlock()
451
452         fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
453         defer fd.Close()
454         if err != nil {
455                 return err
456         }
457
458         data := &xmlTargets{
459                 Version: "1",
460                 Targets: targets,
461         }
462
463         enc := xml.NewEncoder(fd)
464         enc.Indent("", "  ")
465         return enc.Encode(data)
466 }