diff options
Diffstat (limited to 'lib/xdsserver/targets.go')
-rw-r--r-- | lib/xdsserver/targets.go | 466 |
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) +} |