summaryrefslogtreecommitdiffstats
path: root/lib/xdsserver/targets.go
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 18:45:15 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 18:45:15 +0100
commit65e09e831cf13343ac713fbf15281174d1f13a94 (patch)
tree0176a263ecf6d1059a272007c6f450770f5792ca /lib/xdsserver/targets.go
parente97eebc18d726aa55738d7e19513491cf58a6e3a (diff)
Added target and terminal support.v1.1.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'lib/xdsserver/targets.go')
-rw-r--r--lib/xdsserver/targets.go466
1 files changed, 466 insertions, 0 deletions
diff --git a/lib/xdsserver/targets.go b/lib/xdsserver/targets.go
new file mode 100644
index 0000000..663233d
--- /dev/null
+++ b/lib/xdsserver/targets.go
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xdsserver
+
+import (
+ "encoding/xml"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig"
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+ socketio "github.com/googollee/go-socket.io"
+ "github.com/syncthing/syncthing/lib/sync"
+)
+
+// Targets Represent a XDS targets
+type Targets struct {
+ *Context
+ fileOnDisk string
+ tgts map[string]*ITARGET
+ terminals map[string]*Terminals
+}
+
+// Mutex to make add/delete atomic
+var tcMutex = sync.NewMutex()
+
+/***
+ * Targets
+ ***/
+
+// TargetsConstructor Create a new instance of Model Target
+func TargetsConstructor(ctx *Context) *Targets {
+ file, _ := xdsconfig.TargetsConfigFilenameGet()
+ return &Targets{
+ Context: ctx,
+ fileOnDisk: file,
+ tgts: make(map[string]*ITARGET),
+ terminals: make(map[string]*Terminals),
+ }
+}
+
+// LoadConfig Load targets configuration from disk
+func (t *Targets) LoadConfig() error {
+ var tgts []xsapiv1.TargetConfig
+
+ if t.fileOnDisk != "" {
+ t.Log.Infof("Use target config file: %s", t.fileOnDisk)
+ err := targetsConfigRead(t.fileOnDisk, &tgts)
+ if err != nil {
+ if strings.HasPrefix(err.Error(), "EOF") {
+ t.Log.Warnf("Empty target config file")
+ } else if strings.HasPrefix(err.Error(), "No target config") {
+ t.Log.Warnf(err.Error())
+ } else {
+ return err
+ }
+ }
+ } else {
+ t.Log.Warnf("Targets config filename not set")
+ }
+
+ // Update targets
+ t.Log.Infof("Loading initial targets config: %d targets found", len(tgts))
+ for _, tc := range tgts {
+ if _, err := t.createUpdate(tc, false, true); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// SaveConfig Save targets configuration to disk
+func (t *Targets) SaveConfig() error {
+ if t.fileOnDisk == "" {
+ return fmt.Errorf("Targets config filename not set")
+ }
+
+ // FIXME: buffered save or avoid to write on disk each time
+ return targetsConfigWrite(t.fileOnDisk, t.getConfigArrUnsafe())
+}
+
+// ResolveID Complete a Target ID (helper for user that can use partial ID value)
+func (t *Targets) ResolveID(id string) (string, error) {
+ if id == "" {
+ return "", nil
+ }
+
+ match := []string{}
+ for iid := range t.tgts {
+ if strings.HasPrefix(iid, id) {
+ match = append(match, iid)
+ }
+ }
+
+ if len(match) == 1 {
+ return match[0], nil
+ } else if len(match) == 0 {
+ return id, fmt.Errorf("Unknown id")
+ }
+ return id, fmt.Errorf("Multiple IDs found %v", match)
+}
+
+// Get returns the target config or nil if not existing
+func (t *Targets) Get(id string) *ITARGET {
+ if id == "" {
+ return nil
+ }
+ tc, exist := t.tgts[id]
+ if !exist {
+ return nil
+ }
+ return tc
+}
+
+// GetConfigArr returns the config of all targets as an array
+func (t *Targets) GetConfigArr() []xsapiv1.TargetConfig {
+ tcMutex.Lock()
+ defer tcMutex.Unlock()
+
+ return t.getConfigArrUnsafe()
+}
+
+// getConfigArrUnsafe Same as GetConfigArr without mutex protection
+func (t *Targets) getConfigArrUnsafe() []xsapiv1.TargetConfig {
+ conf := []xsapiv1.TargetConfig{}
+ for _, v := range t.tgts {
+ conf = append(conf, (*v).GetConfig())
+ }
+ return conf
+}
+
+// Add adds a new target
+func (t *Targets) Add(newT xsapiv1.TargetConfig) (*xsapiv1.TargetConfig, error) {
+ return t.createUpdate(newT, true, false)
+}
+
+// CreateUpdate creates or update a target
+func (t *Targets) createUpdate(newT xsapiv1.TargetConfig, create bool, initial bool) (*xsapiv1.TargetConfig, error) {
+ var err error
+
+ tcMutex.Lock()
+ defer tcMutex.Unlock()
+
+ // Sanity check
+ if _, exist := t.tgts[newT.ID]; exist {
+ return nil, fmt.Errorf("ID already exists")
+ }
+
+ var tgt ITARGET
+ switch newT.Type {
+ case xsapiv1.TypeTgtStandard:
+ tgt = NewTargetStandard(t.Context)
+ default:
+ return nil, fmt.Errorf("Unsupported target type")
+ }
+
+ // Allocate a new UUID
+ if create {
+ newT.ID = tgt.NewUID("")
+ }
+ if !create && newT.ID == "" {
+ return nil, fmt.Errorf("Cannot update target with null ID")
+ }
+
+ if newT.Name == "" {
+ newT.Name = "Target"
+ if len(newT.ID) > 8 {
+ newT.Name += "_" + newT.ID[0:8]
+ } else {
+ newT.Name += "_" + newT.ID
+ }
+ }
+
+ // Call terminals constructor the first time
+ var terms *Terminals
+ if _, exist := t.terminals[newT.ID]; !exist {
+ terms = TerminalsConstructor(t.Context)
+ t.terminals[newT.ID] = terms
+ } else {
+ terms = t.terminals[newT.ID]
+ }
+
+ var newTarget *xsapiv1.TargetConfig
+ if create {
+ // Add target
+ if newTarget, err = tgt.Add(newT, terms); err != nil {
+ newT.Status = xsapiv1.StatusTgtErrorConfig
+ log.Printf("ERROR Adding target: %v\n", err)
+ return newTarget, err
+ }
+ } else {
+ // Just update target config
+ if newTarget, err = tgt.Setup(newT, terms); err != nil {
+ newT.Status = xsapiv1.StatusTgtErrorConfig
+ log.Printf("ERROR Updating target: %v\n", err)
+ return newTarget, err
+ }
+ }
+
+ // Create terminals
+ for _, tc := range newT.Terms {
+ _, err := t.CreateUpdateTerminal(newT.ID, tc, initial)
+ if err != nil {
+ return newTarget, err
+ }
+ }
+
+ // Add to folders list
+ t.tgts[newT.ID] = &tgt
+
+ // Save config on disk
+ if !initial {
+ if err := t.SaveConfig(); err != nil {
+ return newTarget, err
+ }
+ }
+
+ newTgt := tgt.GetConfig()
+ return &newTgt, nil
+}
+
+// Delete deletes a specific target
+func (t *Targets) Delete(id string) (xsapiv1.TargetConfig, error) {
+ var err error
+
+ tcMutex.Lock()
+ defer tcMutex.Unlock()
+
+ tgc := xsapiv1.TargetConfig{}
+ tc, exist := t.tgts[id]
+ if !exist {
+ return tgc, fmt.Errorf("unknown id")
+ }
+
+ tgc = (*tc).GetConfig()
+
+ if err = (*tc).Delete(); err != nil {
+ return tgc, err
+ }
+
+ delete(t.tgts, id)
+
+ // Save config on disk
+ err = t.SaveConfig()
+
+ return tgc, err
+}
+
+/***
+ * Terminals
+ ***/
+
+// GetTerminalsArr Return list of existing terminals
+func (t *Targets) GetTerminalsArr(targetID string) ([]xsapiv1.TerminalConfig, error) {
+ arr := []xsapiv1.TerminalConfig{}
+
+ tm, exist := t.terminals[targetID]
+ if !exist {
+ return arr, fmt.Errorf("unknown target id")
+ }
+
+ for _, tt := range (*tm).terms {
+ arr = append(arr, (*tt).GetConfig())
+ }
+ return arr, nil
+}
+
+// GetTerminal Return info of a specific terminal
+func (t *Targets) GetTerminal(targetID, termID string) (*ITERMINAL, error) {
+ tm, exist := t.terminals[targetID]
+ if !exist {
+ return nil, fmt.Errorf("unknown target id")
+ }
+ term, exist := (*tm).terms[termID]
+ if !exist {
+ return nil, fmt.Errorf("unknown terminal id")
+ }
+ return term, nil
+}
+
+// ResolveTerminalID Complete a Terminal ID (helper for user that can use partial ID value)
+func (t *Targets) ResolveTerminalID(termID string) (string, error) {
+ if termID == "" {
+ return "", fmt.Errorf("unknown terminal id")
+ }
+
+ match := []string{}
+ for _, tm := range t.terminals {
+ for tid := range tm.terms {
+ if strings.HasPrefix(tid, termID) {
+ match = append(match, tid)
+ }
+ }
+ }
+
+ if len(match) == 1 {
+ return match[0], nil
+ } else if len(match) == 0 {
+ return termID, fmt.Errorf("Unknown id")
+ }
+ return termID, fmt.Errorf("Multiple IDs found %v", match)
+}
+
+// CreateUpdateTerminal Create or Update a target terminal definition
+func (t *Targets) CreateUpdateTerminal(targetID string, tmCfg xsapiv1.TerminalConfig, initial bool) (*xsapiv1.TerminalConfig, error) {
+
+ var term *xsapiv1.TerminalConfig
+
+ iTerm, err := t.GetTerminal(targetID, tmCfg.ID)
+ if err != nil && strings.Contains(err.Error(), "unknown target") {
+ return nil, err
+ }
+
+ if iTerm != nil {
+ // Update terminal config
+ term = (*iTerm).UpdateConfig(tmCfg)
+ } else {
+ // Auto create a new terminal when needed
+ var err error
+ if term, err = t.terminals[targetID].New(tmCfg, targetID); err != nil {
+ return nil, err
+ }
+ }
+
+ term.Status = xsapiv1.StatusTermEnable
+
+ // Save config on disk
+ if !initial {
+ if err := t.SaveConfig(); err != nil {
+ return term, err
+ }
+ }
+
+ return term, nil
+}
+
+// DeleteTerminal Delete a target terminal definition
+func (t *Targets) DeleteTerminal(targetID, termID string) (*xsapiv1.TerminalConfig, error) {
+ terms, exist := t.terminals[targetID]
+ if !exist {
+ return nil, fmt.Errorf("unknown target id")
+ }
+
+ term, err := (*terms).Free(termID)
+ if err != nil {
+ return term, err
+ }
+
+ // Save config on disk
+ if err := t.SaveConfig(); err != nil {
+ return term, err
+ }
+
+ return term, nil
+}
+
+// OpenTerminal Open a target terminal
+func (t *Targets) OpenTerminal(targetID, termID string, sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) {
+ terms, exist := t.terminals[targetID]
+ if !exist {
+ return nil, fmt.Errorf("unknown target id")
+ }
+ return (*terms).Open(termID, sock, sessID)
+}
+
+// CloseTerminal Close a target terminal
+func (t *Targets) CloseTerminal(targetID, termID string) (*xsapiv1.TerminalConfig, error) {
+ terms, exist := t.terminals[targetID]
+ if !exist {
+ return nil, fmt.Errorf("unknown target id")
+ }
+ return (*terms).Close(termID)
+}
+
+// ResizeTerminal Set size (row+col) of a target terminal
+func (t *Targets) ResizeTerminal(targetID, termID string, cols, rows uint16) (*xsapiv1.TerminalConfig, error) {
+ terms, exist := t.terminals[targetID]
+ if !exist {
+ return nil, fmt.Errorf("unknown target id")
+ }
+ return (*terms).Resize(termID, cols, rows)
+}
+
+// SignalTerminal Send a signal to a target terminal
+func (t *Targets) SignalTerminal(targetID, termID, sigNum string) error {
+ terms, exist := t.terminals[targetID]
+ if !exist {
+ return fmt.Errorf("unknown target id")
+ }
+ return (*terms).Signal(termID, sigNum)
+}
+
+/**
+ * Private functions
+ **/
+
+// Use XML format and not json to be able to save/load all fields including
+// ones that are masked in json (IOW defined with `json:"-"`)
+type xmlTargets struct {
+ XMLName xml.Name `xml:"targets"`
+ Version string `xml:"version,attr"`
+ Targets []xsapiv1.TargetConfig `xml:"targets"`
+}
+
+// targetsConfigRead reads targets config from disk
+func targetsConfigRead(file string, targets *[]xsapiv1.TargetConfig) error {
+ if !common.Exists(file) {
+ return fmt.Errorf("No target config file found (%s)", file)
+ }
+
+ ffMutex.Lock()
+ defer ffMutex.Unlock()
+
+ fd, err := os.Open(file)
+ defer fd.Close()
+ if err != nil {
+ return err
+ }
+
+ data := xmlTargets{}
+ err = xml.NewDecoder(fd).Decode(&data)
+ if err == nil {
+ *targets = data.Targets
+ }
+ return err
+}
+
+// targetsConfigWrite writes targets config on disk
+func targetsConfigWrite(file string, targets []xsapiv1.TargetConfig) error {
+ ffMutex.Lock()
+ defer ffMutex.Unlock()
+
+ fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+ defer fd.Close()
+ if err != nil {
+ return err
+ }
+
+ data := &xmlTargets{
+ Version: "1",
+ Targets: targets,
+ }
+
+ enc := xml.NewEncoder(fd)
+ enc.Indent("", " ")
+ return enc.Encode(data)
+}