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