aboutsummaryrefslogtreecommitdiffstats
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
parente97eebc18d726aa55738d7e19513491cf58a6e3a (diff)
Added target and terminal support.v1.1.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--.vscode/settings.json5
-rw-r--r--glide.yaml2
-rw-r--r--lib/xdsconfig/fileconfig.go7
-rw-r--r--lib/xdsserver/apiv1-exec.go3
-rw-r--r--lib/xdsserver/apiv1-targets.go288
-rw-r--r--lib/xdsserver/apiv1.go15
-rw-r--r--lib/xdsserver/events.go4
-rw-r--r--lib/xdsserver/folders.go8
-rw-r--r--lib/xdsserver/sdks.go4
-rw-r--r--lib/xdsserver/sessions.go6
-rw-r--r--lib/xdsserver/target-interface.go29
-rw-r--r--lib/xdsserver/target-standard.go89
-rw-r--r--lib/xdsserver/targets.go466
-rw-r--r--lib/xdsserver/terminal-interface.go33
-rw-r--r--lib/xdsserver/terminal-ssh.go265
-rw-r--r--lib/xdsserver/terminals.go159
-rw-r--r--lib/xdsserver/webserver.go11
-rw-r--r--lib/xdsserver/xdsserver.go19
-rw-r--r--lib/xsapiv1/targets.go124
19 files changed, 1513 insertions, 24 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9367870..7787bfa 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -28,8 +28,9 @@
"rpath", "WSID", "sess", "IXDS", "xdsconfig", "xdsserver", "mfolder",
"inotify", "Inot", "pname", "pkill", "sdkid", "CLOUDSYNC", "xdsagent",
"gdbserver", "golib", "eows", "mfolders", "IFOLDER", "flds", "dflt",
- "stconfig", "reflectme", "franciscocpg", "crosssdk", "urfave", "EXEPATH", "conv", "Sillyf", "xsapiv",
- "EVTSDK", "zillode", "gerrit"
+ "stconfig", "reflectme", "franciscocpg", "crosssdk", "urfave", "EXEPATH",
+ "conv", "Sillyf", "xsapiv", "EVTSDK", "zillode", "gerrit", "ITARGET",
+ "tgts", "ITERMINAL", "unregister", "Cifs"
],
"editor.insertSpaces": true,
"editor.detectIndentation": true
diff --git a/glide.yaml b/glide.yaml
index 0b12db8..70502be 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -25,7 +25,7 @@ import:
- package: github.com/satori/go.uuid
version: ^1.1.0
- package: gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git
- version: ^0.1.0
+ version: ~0.2.0
subpackages:
- golib/common
- golib/eows
diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go
index ef207ec..86f39b5 100644
--- a/lib/xdsconfig/fileconfig.go
+++ b/lib/xdsconfig/fileconfig.go
@@ -38,6 +38,8 @@ const (
ServerDataFilename = "server-data.xml"
// FoldersConfigFilename Folders config filename
FoldersConfigFilename = "server-config_folders.xml"
+ // TargetsConfigFilename Targets config filename
+ TargetsConfigFilename = "server-config_targets.xml"
)
// SyncThingConf definition
@@ -182,6 +184,11 @@ func FoldersConfigFilenameGet() (string, error) {
return configFilenameGet(FoldersConfigFilename)
}
+// TargetsConfigFilenameGet Return the TargetsConfig filename
+func TargetsConfigFilenameGet() (string, error) {
+ return configFilenameGet(TargetsConfigFilename)
+}
+
// ServerDataFilenameGet Return the ServerData filename
func ServerDataFilenameGet() (string, error) {
return configFilenameGet(ServerDataFilename)
diff --git a/lib/xdsserver/apiv1-exec.go b/lib/xdsserver/apiv1-exec.go
index 2337de6..327c4c5 100644
--- a/lib/xdsserver/apiv1-exec.go
+++ b/lib/xdsserver/apiv1-exec.go
@@ -136,6 +136,7 @@ func (s *APIService) execCmd(c *gin.Context) {
// Create new execution over WS context
execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, args.CmdID)
execWS.Log = s.Log
+ execWS.OutSplit = eows.SplitChar
// Append client project dir to environment
execWS.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.ClientPath)
@@ -180,7 +181,7 @@ func (s *APIService) execCmd(c *gin.Context) {
// IO socket can be nil when disconnected
so := s.sessions.IOSocketGet(e.Sid)
if so == nil {
- s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.ExecOutEvent, e.Sid, e.CmdID)
+ s.Log.Infof("%s not emitted: WS closed (sid:%s, CmdID:%s)", xsapiv1.ExecOutEvent, e.Sid, e.CmdID)
return
}
diff --git a/lib/xdsserver/apiv1-targets.go b/lib/xdsserver/apiv1-targets.go
new file mode 100644
index 0000000..978dc75
--- /dev/null
+++ b/lib/xdsserver/apiv1-targets.go
@@ -0,0 +1,288 @@
+/*
+ * 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 (
+ "net/http"
+
+ common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+ "github.com/gin-gonic/gin"
+)
+
+/***
+ * Targets
+ ***/
+
+// getTargets returns all targets configuration
+func (s *APIService) getTargets(c *gin.Context) {
+ c.JSON(http.StatusOK, s.targets.GetConfigArr())
+}
+
+// getTarget returns a specific target configuration
+func (s *APIService) getTarget(c *gin.Context) {
+ id, err := s.targets.ResolveID(c.Param("id"))
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ f := s.targets.Get(id)
+ if f == nil {
+ common.APIError(c, "Invalid id")
+ return
+ }
+
+ c.JSON(http.StatusOK, (*f).GetConfig())
+}
+
+// addTarget adds a new target to server config
+func (s *APIService) addTarget(c *gin.Context) {
+ var cfgArg xsapiv1.TargetConfig
+ if c.BindJSON(&cfgArg) != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ s.Log.Debugln("Add target config: ", cfgArg)
+
+ newTgt, err := s.targets.Add(cfgArg)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, newTgt)
+}
+
+// delTarget deletes target from server config
+func (s *APIService) delTarget(c *gin.Context) {
+ id, err := s.targets.ResolveID(c.Param("id"))
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ s.Log.Debugln("Delete target id ", id)
+
+ delEntry, err := s.targets.Delete(id)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, delEntry)
+}
+
+/***
+ * Terminals
+ ***/
+// getTgtTerms Get list of all terminals
+func (s *APIService) getTgtTerms(c *gin.Context) {
+ id, err := s.targets.ResolveID(c.Param("id"))
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ res, err := s.targets.GetTerminalsArr(id)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, res)
+}
+
+// getTgtTerm Get info a terminal
+func (s *APIService) getTgtTerm(c *gin.Context) {
+ id, tid, err := s._decodeTermArgs(c)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ iTerm, err := s.targets.GetTerminal(id, tid)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, (*iTerm).GetConfig())
+}
+
+// createTgtTerm Create a new terminal
+func (s *APIService) createTgtTerm(c *gin.Context) {
+ s.updateTgtTerm(c)
+}
+
+// updateTgtTerm Update terminal config
+func (s *APIService) updateTgtTerm(c *gin.Context) {
+ var cfgArg xsapiv1.TerminalConfig
+
+ tgtID, termID, err := s._decodeTermArgs(c)
+ if tgtID == "" && err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ if err := c.BindJSON(&cfgArg); err != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+ if cfgArg.ID == "" {
+ cfgArg.ID = termID
+ }
+ if termID != "" && cfgArg.ID != termID {
+ common.APIError(c, "Invalid arguments, inconsistent terminal id ")
+ return
+ }
+ s.Log.Debugln("Add or Update terminal config: ", cfgArg)
+ term, err := s.targets.CreateUpdateTerminal(tgtID, cfgArg, false)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, term)
+}
+
+// delTgtTerm Delete a terminal
+func (s *APIService) delTgtTerm(c *gin.Context) {
+
+ tgtID, termID, err := s._decodeTermArgs(c)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ term, err := s.targets.DeleteTerminal(tgtID, termID)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, term)
+}
+
+// openTgtTerm Open a target terminal/console
+func (s *APIService) openTgtTerm(c *gin.Context) {
+
+ id, tid, err := s._decodeTermArgs(c)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ // Retrieve session info
+ sess := s.sessions.Get(c)
+ if sess == nil {
+ common.APIError(c, "Unknown sessions")
+ return
+ }
+ sock := sess.IOSocket
+ if sock == nil {
+ common.APIError(c, "Websocket not established")
+ return
+ }
+
+ term, err := s.targets.OpenTerminal(id, tid, sock, sess.ID)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, term)
+}
+
+// closeTgtTerm Close a terminal
+func (s *APIService) closeTgtTerm(c *gin.Context) {
+ id, tid, err := s._decodeTermArgs(c)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ term, err := s.targets.CloseTerminal(id, tid)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, term)
+}
+
+// resizeTgtTerm Resize a terminal
+func (s *APIService) resizeTgtTerm(c *gin.Context) {
+ var sizeArg xsapiv1.TerminalResizeArgs
+
+ id, tid, err := s._decodeTermArgs(c)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ if err := c.BindJSON(&sizeArg); err != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ term, err := s.targets.ResizeTerminal(id, tid, sizeArg.Cols, sizeArg.Rows)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, term)
+}
+
+// signalTgtTerm Send a signal to a terminal
+func (s *APIService) signalTgtTerm(c *gin.Context) {
+ var sigArg xsapiv1.TerminalSignalArgs
+
+ id, tid, err := s._decodeTermArgs(c)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ sigName := c.Param("sig")
+ if sigName == "" {
+ if err := c.BindJSON(&sigArg); err != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+ sigName = sigArg.Signal
+ }
+ if sigName == "" {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ if err := s.targets.SignalTerminal(id, tid, sigName); err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, "")
+}
+
+// _decodeTermArgs Helper to decode arguments of Terminal routes
+func (s *APIService) _decodeTermArgs(c *gin.Context) (string, string, error) {
+ id, err := s.targets.ResolveID(c.Param("id"))
+ if err != nil {
+ return "", "", err
+ }
+
+ termID, err := s.targets.ResolveTerminalID(c.Param("tid"))
+ if err != nil {
+ return id, "", err
+ }
+
+ return id, termID, nil
+}
diff --git a/lib/xdsserver/apiv1.go b/lib/xdsserver/apiv1.go
index 67d09b5..e0bfa7f 100644
--- a/lib/xdsserver/apiv1.go
+++ b/lib/xdsserver/apiv1.go
@@ -63,5 +63,20 @@ func NewAPIV1(ctx *Context) *APIService {
s.apiRouter.POST("/events/register", s.eventsRegister)
s.apiRouter.POST("/events/unregister", s.eventsUnRegister)
+ s.apiRouter.GET("/targets", s.getTargets)
+ s.apiRouter.GET("/targets/:id", s.getTarget)
+ s.apiRouter.POST("/targets", s.addTarget)
+ s.apiRouter.DELETE("/targets/:id", s.delTarget)
+ s.apiRouter.GET("/targets/:id/terminals", s.getTgtTerms)
+ s.apiRouter.GET("/targets/:id/terminals/:tid", s.getTgtTerm)
+ s.apiRouter.POST("/targets/:id/terminals", s.createTgtTerm)
+ s.apiRouter.PUT("/targets/:id/terminals/:tid", s.updateTgtTerm)
+ s.apiRouter.DELETE("/targets/:id/terminals/:tid", s.delTgtTerm)
+ s.apiRouter.POST("/targets/:id/terminals/:tid/open", s.openTgtTerm)
+ s.apiRouter.POST("/targets/:id/terminals/:tid/close", s.closeTgtTerm)
+ s.apiRouter.POST("/targets/:id/terminals/:tid/resize", s.resizeTgtTerm)
+ s.apiRouter.POST("/targets/:id/terminals/:tid/signal", s.signalTgtTerm)
+ s.apiRouter.POST("/targets/:id/terminals/:tid/signal/:sig", s.signalTgtTerm)
+
return s
}
diff --git a/lib/xdsserver/events.go b/lib/xdsserver/events.go
index 2528725..0a02ecd 100644
--- a/lib/xdsserver/events.go
+++ b/lib/xdsserver/events.go
@@ -35,8 +35,8 @@ type Events struct {
eventsMap map[string]*EventDef
}
-// NewEvents creates an instance of Events
-func NewEvents(ctx *Context) *Events {
+// EventsConstructor creates an instance of Events
+func EventsConstructor(ctx *Context) *Events {
evMap := make(map[string]*EventDef)
for _, ev := range xsapiv1.EVTAllList {
evMap[ev] = &EventDef{
diff --git a/lib/xdsserver/folders.go b/lib/xdsserver/folders.go
index fa24878..d27a329 100644
--- a/lib/xdsserver/folders.go
+++ b/lib/xdsserver/folders.go
@@ -51,8 +51,8 @@ type RegisteredCB struct {
var fcMutex = sync.NewMutex()
var ffMutex = sync.NewMutex()
-// FoldersNew Create a new instance of Model Folders
-func FoldersNew(ctx *Context) *Folders {
+// FoldersConstructor Create a new instance of Model Folders
+func FoldersConstructor(ctx *Context) *Folders {
file, _ := xdsconfig.FoldersConfigFilenameGet()
return &Folders{
Context: ctx,
@@ -74,7 +74,9 @@ func (f *Folders) LoadConfig() error {
f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
err := foldersConfigRead(f.fileOnDisk, &flds)
if err != nil {
- if strings.HasPrefix(err.Error(), "No folder config") {
+ if strings.HasPrefix(err.Error(), "EOF") {
+ f.Log.Warnf("Empty folder config file")
+ } else if strings.HasPrefix(err.Error(), "No folder config") {
f.Log.Warnf(err.Error())
} else {
return err
diff --git a/lib/xdsserver/sdks.go b/lib/xdsserver/sdks.go
index 6094045..4a7ba84 100644
--- a/lib/xdsserver/sdks.go
+++ b/lib/xdsserver/sdks.go
@@ -38,8 +38,8 @@ type SDKs struct {
stop chan struct{} // signals intentional stop
}
-// NewSDKs creates a new instance of SDKs
-func NewSDKs(ctx *Context) (*SDKs, error) {
+// SDKsConstructor creates a new instance of SDKs
+func SDKsConstructor(ctx *Context) (*SDKs, error) {
s := SDKs{
Context: ctx,
Sdks: make(map[string]*CrossSDK),
diff --git a/lib/xdsserver/sessions.go b/lib/xdsserver/sessions.go
index 69fe819..0c16b99 100644
--- a/lib/xdsserver/sessions.go
+++ b/lib/xdsserver/sessions.go
@@ -59,8 +59,8 @@ type Sessions struct {
stop chan struct{} // signals intentional stop
}
-// NewClientSessions .
-func NewClientSessions(ctx *Context, cookieMaxAge string) *Sessions {
+// ClientSessionsConstructor .
+func ClientSessionsConstructor(ctx *Context, cookieMaxAge string) *Sessions {
ckMaxAge, err := strconv.ParseInt(cookieMaxAge, 10, 0)
if err != nil {
ckMaxAge = 0
@@ -226,7 +226,7 @@ func (s *Sessions) monitorSessMap() {
s.mutex.Lock()
for _, ss := range s.sessMap {
- if ss.expireAt.Sub(time.Now()) < 0 {
+ if ss.expireAt.Sub(time.Now()) <= 0 {
s.Log.Debugf("Delete expired session id: %s", ss.ID)
delete(s.sessMap, ss.ID)
}
diff --git a/lib/xdsserver/target-interface.go b/lib/xdsserver/target-interface.go
new file mode 100644
index 0000000..6d5bd7b
--- /dev/null
+++ b/lib/xdsserver/target-interface.go
@@ -0,0 +1,29 @@
+/*
+ * 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 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+
+// ITARGET Target interface
+type ITARGET interface {
+ NewUID(suffix string) string // Get a new target UUID
+ Add(cfg xsapiv1.TargetConfig, terms *Terminals) (*xsapiv1.TargetConfig, error) // Add a new target
+ Delete() error // Remove a target
+ Setup(prj xsapiv1.TargetConfig, terms *Terminals) (*xsapiv1.TargetConfig, error) // Local setup of the folder
+ GetConfig() xsapiv1.TargetConfig // Get target public configuration
+}
diff --git a/lib/xdsserver/target-standard.go b/lib/xdsserver/target-standard.go
new file mode 100644
index 0000000..2c1b068
--- /dev/null
+++ b/lib/xdsserver/target-standard.go
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017-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 (
+ "fmt"
+
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+ uuid "github.com/satori/go.uuid"
+)
+
+// ITARGET interface implementation for standard targets
+
+// TgtStd .
+type TgtStd struct {
+ *Context
+ TgtConfig xsapiv1.TargetConfig
+ terminals *Terminals
+}
+
+// NewTargetStandard Create a new instance of TgtStd
+func NewTargetStandard(ctx *Context) *TgtStd {
+ t := TgtStd{
+ Context: ctx,
+ TgtConfig: xsapiv1.TargetConfig{
+ Status: xsapiv1.StatusTgtDisable,
+ },
+ }
+ return &t
+}
+
+// NewUID Get a UUID
+func (t *TgtStd) NewUID(suffix string) string {
+ uuid := uuid.NewV1().String()
+ if len(suffix) > 0 {
+ uuid += "_" + suffix
+ }
+ return uuid
+}
+
+// Add a new target
+func (t *TgtStd) Add(cfg xsapiv1.TargetConfig, terms *Terminals) (*xsapiv1.TargetConfig, error) {
+ return t.Setup(cfg, terms)
+}
+
+// Delete a target
+func (t *TgtStd) Delete() error {
+ // nothing to do
+ return nil
+}
+
+// Setup Setup local project config
+func (t *TgtStd) Setup(cfg xsapiv1.TargetConfig, terms *Terminals) (*xsapiv1.TargetConfig, error) {
+
+ if cfg.IP == "" {
+ return nil, fmt.Errorf("IP address must be set")
+ }
+
+ t.TgtConfig = cfg
+ t.terminals = terms
+
+ // FIXME: sanity check test ping IP
+
+ t.TgtConfig.Status = xsapiv1.StatusTgtEnable
+
+ return &t.TgtConfig, nil
+}
+
+// GetConfig Get public part of target config
+func (t *TgtStd) GetConfig() xsapiv1.TargetConfig {
+ // XXX - Need to manually update terminal definition ()
+ t.TgtConfig.Terms = (*t.terminals).GetConfigArr()
+ return t.TgtConfig
+}
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)
+}
diff --git a/lib/xdsserver/terminal-interface.go b/lib/xdsserver/terminal-interface.go
new file mode 100644
index 0000000..8542448
--- /dev/null
+++ b/lib/xdsserver/terminal-interface.go
@@ -0,0 +1,33 @@
+/*
+ * 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 (
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+ socketio "github.com/googollee/go-socket.io"
+)
+
+// ITERMINAL Terminal interface
+type ITERMINAL interface {
+ GetConfig() xsapiv1.TerminalConfig // Get terminal public configuration
+ UpdateConfig(cfg xsapiv1.TerminalConfig) *xsapiv1.TerminalConfig // Update terminal config
+ Open(sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) // Open a terminal session
+ Close() (*xsapiv1.TerminalConfig, error) // Close a terminal session
+ Resize(cols, rows uint16) (*xsapiv1.TerminalConfig, error) // Resize a terminal session
+ Signal(sigName string) error // Send a signal to a terminal session
+}
diff --git a/lib/xdsserver/terminal-ssh.go b/lib/xdsserver/terminal-ssh.go
new file mode 100644
index 0000000..3f4a344
--- /dev/null
+++ b/lib/xdsserver/terminal-ssh.go
@@ -0,0 +1,265 @@
+/*
+ * 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 (
+ "fmt"
+ "strings"
+ "time"
+
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib/eows"
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+ socketio "github.com/googollee/go-socket.io"
+ uuid "github.com/satori/go.uuid"
+)
+
+// ITARGET interface implementation for standard targets
+
+// TermSSH .
+type TermSSH struct {
+ *Context
+ termCfg xsapiv1.TerminalConfig
+ targetID string
+ sshWS *eows.ExecOverWS
+}
+
+// NewTermSSH Create a new instance of TermSSH
+func NewTermSSH(ctx *Context, cfg xsapiv1.TerminalConfig, targetID string) *TermSSH {
+
+ // Allocate and set default settings
+ t := TermSSH{
+ Context: ctx,
+ termCfg: xsapiv1.TerminalConfig{
+ ID: cfg.ID,
+ Name: "ssh",
+ Type: xsapiv1.TypeTermSSH,
+ Status: xsapiv1.StatusTermClose,
+ User: "",
+ Options: []string{""},
+ Cols: 80,
+ Rows: 24,
+ },
+ targetID: targetID,
+ }
+
+ t.UpdateConfig(cfg)
+ return &t
+}
+
+// NewUID Get a UUID
+func (t *TermSSH) _NewUID(suffix string) string {
+ uuid := uuid.NewV1().String()
+ if len(suffix) > 0 {
+ uuid += "_" + suffix
+ }
+ return uuid
+}
+
+// GetConfig Get public part of terminal config
+func (t *TermSSH) GetConfig() xsapiv1.TerminalConfig {
+ return t.termCfg
+}
+
+// UpdateConfig Update terminal config
+func (t *TermSSH) UpdateConfig(newCfg xsapiv1.TerminalConfig) *xsapiv1.TerminalConfig {
+
+ if t.termCfg.ID == "" {
+ if newCfg.ID != "" {
+ t.termCfg.ID = newCfg.ID
+ } else {
+ t.termCfg.ID = t._NewUID("")
+ }
+ }
+ if newCfg.Name != "" {
+ t.termCfg.Name = newCfg.Name
+ }
+ if newCfg.User != "" {
+ t.termCfg.User = newCfg.User
+ }
+ if len(newCfg.Options) > 0 {
+ t.termCfg.Options = newCfg.Options
+ }
+
+ // Adjust terminal size
+ t.Resize(newCfg.Cols, newCfg.Rows)
+
+ return &t.termCfg
+}
+
+// Open a new terminal - execute ssh command and bind stdin/stdout to WebSocket
+func (t *TermSSH) Open(sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) {
+
+ // Get target info to retrieve IP
+ tgt := t.targets.Get(t.targetID)
+ if tgt == nil {
+ return nil, fmt.Errorf("Cannot retrieve target definition")
+ }
+ tgtCfg := (*tgt).GetConfig()
+
+ // Sanity check
+ if tgtCfg.IP == "" {
+ return nil, fmt.Errorf("null target IP")
+ }
+ userStr := ""
+ if t.termCfg.User != "" {
+ userStr = t.termCfg.User + "@"
+ }
+
+ // Compute ssh command
+ cmd := "ssh"
+ cmdID := "ssh_term_" + t.termCfg.ID
+ args := t.termCfg.Options
+ args = append(args, userStr+tgtCfg.IP)
+
+ t.sshWS = eows.New(cmd, args, sock, sessID, cmdID)
+ t.sshWS.Log = t.Log
+ t.sshWS.OutSplit = eows.SplitChar
+ t.sshWS.PtsMode = true
+
+ // Define callback for input (stdin)
+ t.sshWS.InputEvent = xsapiv1.TerminalInEvent
+ t.sshWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) {
+ t.Log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1))
+
+ // Handle Ctrl-D
+ if len(stdin) == 1 && stdin == "\x04" {
+ // Close stdin
+ errMsg := fmt.Errorf("close stdin: %v", stdin)
+ return "", errMsg
+ }
+
+ return stdin, nil
+ }
+
+ // Define callback for output (stdout+stderr)
+ t.sshWS.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) {
+ // IO socket can be nil when disconnected
+ so := t.sessions.IOSocketGet(e.Sid)
+ if so == nil {
+ t.Log.Infof("%s not emitted: WS closed (sid:%s, CmdID:%s)", xsapiv1.TerminalOutEvent, e.Sid, e.CmdID)
+ return
+ }
+
+ // Retrieve project ID and RootPath
+ data := e.UserData
+ termID := (*data)["ID"].(string)
+
+ t.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - termID:%s", xsapiv1.TerminalOutEvent, e.Sid[4:], e.CmdID, termID)
+ if stdout != "" {
+ t.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
+ }
+ if stderr != "" {
+ t.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
+ }
+
+ // FIXME replace by .BroadcastTo a room
+ err := (*so).Emit(xsapiv1.TerminalOutEvent, xsapiv1.TerminalOutMsg{
+ TermID: termID,
+ Timestamp: time.Now().String(),
+ Stdout: stdout,
+ Stderr: stderr,
+ })
+ if err != nil {
+ t.Log.Errorf("WS Emit : %v", err)
+ }
+ }
+
+ // Define callback for output
+ t.sshWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) {
+ t.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err)
+
+ // IO socket can be nil when disconnected
+ so := t.sessions.IOSocketGet(e.Sid)
+ if so == nil {
+ t.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.TerminalExitEvent, e.CmdID)
+ return
+ }
+
+ // Retrieve project ID and RootPath
+ data := e.UserData
+ termID := (*data)["ID"].(string)
+
+ // FIXME replace by .BroadcastTo a room
+ errSoEmit := (*so).Emit(xsapiv1.TerminalExitEvent, xsapiv1.TerminalExitMsg{
+ TermID: termID,
+ Timestamp: time.Now().String(),
+ Code: code,
+ Error: err,
+ })
+ if errSoEmit != nil {
+ t.Log.Errorf("WS Emit : %v", errSoEmit)
+ }
+
+ t.termCfg.Status = xsapiv1.StatusTermClose
+ t.sshWS = nil
+ }
+
+ // data (used within callbacks)
+ data := make(map[string]interface{})
+ data["ID"] = t.termCfg.ID
+ t.sshWS.UserData = &data
+
+ // Start ssh command
+ t.Log.Infof("Execute [Cmd ID %s]: %v %v", t.sshWS.CmdID, t.sshWS.Cmd, t.sshWS.Args)
+
+ if err := t.sshWS.Start(); err != nil {
+ return &t.termCfg, err
+ }
+
+ t.termCfg.Status = xsapiv1.StatusTermOpen
+
+ return &t.termCfg, nil
+}
+
+// Close a terminal
+func (t *TermSSH) Close() (*xsapiv1.TerminalConfig, error) {
+ // nothing to do when not open
+ if t.termCfg.Status != xsapiv1.StatusTermOpen {
+ return &t.termCfg, nil
+ }
+
+ err := t.sshWS.Signal("SIGTERM")
+
+ return &t.termCfg, err
+}
+
+// Resize a terminal
+func (t *TermSSH) Resize(cols, rows uint16) (*xsapiv1.TerminalConfig, error) {
+ if t.sshWS == nil {
+ return &t.termCfg, fmt.Errorf("ssh session not initialized")
+ }
+
+ if cols > 0 {
+ t.termCfg.Cols = cols
+ }
+ if rows > 0 {
+ t.termCfg.Rows = rows
+ }
+
+ err := t.sshWS.TerminalSetSize(t.termCfg.Rows, t.termCfg.Cols)
+ if err != nil {
+ t.Log.Errorf("Error ssh TerminalSetSize: %v", err)
+ }
+
+ return &t.termCfg, err
+}
+
+// Signal Send a signal to a terminal
+func (t *TermSSH) Signal(sigName string) error {
+ return t.sshWS.Signal(sigName)
+}
diff --git a/lib/xdsserver/terminals.go b/lib/xdsserver/terminals.go
new file mode 100644
index 0000000..36623ab
--- /dev/null
+++ b/lib/xdsserver/terminals.go
@@ -0,0 +1,159 @@
+/*
+ * 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 (
+ "fmt"
+
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
+ socketio "github.com/googollee/go-socket.io"
+ "github.com/syncthing/syncthing/lib/sync"
+)
+
+// Terminals Represent a XDS terminals
+type Terminals struct {
+ *Context
+ terms map[string]*ITERMINAL
+}
+
+// Mutex to make add/delete atomic
+var tmMutex = sync.NewMutex()
+
+// TerminalsConstructor Create a new instance of Model Terminal
+func TerminalsConstructor(ctx *Context) *Terminals {
+ return &Terminals{
+ Context: ctx,
+ terms: make(map[string]*ITERMINAL),
+ }
+}
+
+// New Create a new terminal
+func (t *Terminals) New(cfg xsapiv1.TerminalConfig, targetID string) (*xsapiv1.TerminalConfig, error) {
+
+ tmMutex.Lock()
+ defer tmMutex.Unlock()
+
+ var newT ITERMINAL
+
+ // For now, only SSH term is supported
+ switch cfg.Type {
+ case xsapiv1.TypeTermSSH:
+ newT = NewTermSSH(t.Context, cfg, targetID)
+ default:
+ return nil, fmt.Errorf("terminal type not set")
+ }
+
+ termCfg := newT.GetConfig()
+
+ t.terms[termCfg.ID] = &newT
+
+ return &termCfg, nil
+}
+
+// Free a specific terminal
+func (t *Terminals) Free(id string) (*xsapiv1.TerminalConfig, error) {
+
+ tmMutex.Lock()
+ defer tmMutex.Unlock()
+
+ tc := t.Get(id)
+ if tc == nil {
+ return nil, fmt.Errorf("Unknown id")
+ }
+
+ if _, err := (*tc).Close(); err != nil {
+ return nil, err
+ }
+
+ resTerm := (*tc).GetConfig()
+
+ delete(t.terms, id)
+
+ return &resTerm, nil
+}
+
+// Get returns the terminal config or nil if not existing
+func (t *Terminals) Get(id string) *ITERMINAL {
+ if id == "" {
+ return nil
+ }
+ tc, exist := t.terms[id]
+ if !exist {
+ return nil
+ }
+ return tc
+}
+
+// GetConfigArr returns the config of all terminals as an array
+func (t *Terminals) GetConfigArr() []xsapiv1.TerminalConfig {
+ tmMutex.Lock()
+ defer tmMutex.Unlock()
+
+ return t.getConfigArrUnsafe()
+}
+
+// getConfigArrUnsafe Same as GetConfigArr without mutex protection
+func (t *Terminals) getConfigArrUnsafe() []xsapiv1.TerminalConfig {
+ conf := []xsapiv1.TerminalConfig{}
+ for _, v := range t.terms {
+ conf = append(conf, (*v).GetConfig())
+ }
+ return conf
+}
+
+// Open adds a new terminal
+func (t *Terminals) Open(id string, sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) {
+ tc := t.Get(id)
+ if tc == nil {
+ return nil, fmt.Errorf("Unknown id")
+ }
+ return (*tc).Open(sock, sessID)
+}
+
+// Close a specific terminal
+func (t *Terminals) Close(id string) (*xsapiv1.TerminalConfig, error) {
+ tc := t.Get(id)
+ if tc == nil {
+ return nil, fmt.Errorf("Unknown id")
+ }
+ return (*tc).Close()
+}
+
+// Resize a specific terminal
+func (t *Terminals) Resize(id string, cols, rows uint16) (*xsapiv1.TerminalConfig, error) {
+ tmMutex.Lock()
+ defer tmMutex.Unlock()
+
+ tc := t.Get(id)
+ if tc == nil {
+ return nil, fmt.Errorf("Unknown id")
+ }
+ return (*tc).Resize(cols, rows)
+}
+
+// Signal Send a Signal a specific terminal
+func (t *Terminals) Signal(id, sigName string) error {
+ tmMutex.Lock()
+ defer tmMutex.Unlock()
+
+ tc := t.Get(id)
+ if tc == nil {
+ return fmt.Errorf("Unknown id")
+ }
+ return (*tc).Signal(sigName)
+}
diff --git a/lib/xdsserver/webserver.go b/lib/xdsserver/webserver.go
index f1c88d2..24456b9 100644
--- a/lib/xdsserver/webserver.go
+++ b/lib/xdsserver/webserver.go
@@ -43,8 +43,8 @@ type WebServer struct {
const indexFilename = "index.html"
-// NewWebServer creates an instance of WebServer
-func NewWebServer(ctx *Context) *WebServer {
+// WebServerConstructor creates an instance of WebServer
+func WebServerConstructor(ctx *Context) *WebServer {
// Setup logging for gin router
if ctx.Log.Level == logrus.DebugLevel {
@@ -183,11 +183,12 @@ func (s *WebServer) socketHandler(c *gin.Context) {
}
s.sIOServer.On("connection", func(so socketio.Socket) {
- s.Log.Debugf("WS Connected (SID=%v)", so.Id())
- s.sessions.UpdateIOSocket(sess.ID, &so)
+ sessID := sess.ID
+ s.Log.Debugf("WS Connected (sessID=%v, SID=%v)", sessID, so.Id())
+ s.sessions.UpdateIOSocket(sessID, &so)
so.On("disconnection", func() {
- s.Log.Debugf("WS disconnected (SID=%v)", so.Id())
+ s.Log.Debugf("WS disconnected (sessID=%v, SID=%v)", sessID, so.Id())
s.sessions.UpdateIOSocket(sess.ID, nil)
})
})
diff --git a/lib/xdsserver/xdsserver.go b/lib/xdsserver/xdsserver.go
index bb8f755..1079eba 100644
--- a/lib/xdsserver/xdsserver.go
+++ b/lib/xdsserver/xdsserver.go
@@ -48,6 +48,7 @@ type Context struct {
SThgInotCmd *exec.Cmd
mfolders *Folders
sdks *SDKs
+ targets *Targets
WWWServer *WebServer
sessions *Sessions
events *Events
@@ -129,7 +130,7 @@ func (ctx *Context) Run() (int, error) {
}
// Create events management
- ctx.events = NewEvents(ctx)
+ ctx.events = EventsConstructor(ctx)
// Create syncthing instance when section "syncthing" is present in server-config.json
if ctx.Config.FileConf.SThgConf != nil {
@@ -178,7 +179,7 @@ func (ctx *Context) Run() (int, error) {
}
// Init model folder
- ctx.mfolders = FoldersNew(ctx)
+ ctx.mfolders = FoldersConstructor(ctx)
// Load initial folders config from disk
if err := ctx.mfolders.LoadConfig(); err != nil {
@@ -186,16 +187,24 @@ func (ctx *Context) Run() (int, error) {
}
// Init cross SDKs
- ctx.sdks, err = NewSDKs(ctx)
+ ctx.sdks, err = SDKsConstructor(ctx)
if err != nil {
return -6, err
}
+ // Init target and terminals model
+ ctx.targets = TargetsConstructor(ctx)
+
+ // Load initial target & terminal config
+ if err := ctx.targets.LoadConfig(); err != nil {
+ return -6, err
+ }
+
// Create Web Server
- ctx.WWWServer = NewWebServer(ctx)
+ ctx.WWWServer = WebServerConstructor(ctx)
// Sessions manager
- ctx.sessions = NewClientSessions(ctx, cookieMaxAge)
+ ctx.sessions = ClientSessionsConstructor(ctx, cookieMaxAge)
// Run Web Server until exit requested (blocking call)
if err = ctx.WWWServer.Serve(); err != nil {
diff --git a/lib/xsapiv1/targets.go b/lib/xsapiv1/targets.go
new file mode 100644
index 0000000..3f2b3c4
--- /dev/null
+++ b/lib/xsapiv1/targets.go
@@ -0,0 +1,124 @@
+/*
+ * 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 xsapiv1
+
+/**
+ * Target
+ **/
+
+// TargetType definition
+type TargetType string
+
+const (
+ // TypeTgtStandard Standard target type
+ TypeTgtStandard = "standard"
+)
+
+// Target Status definition
+const (
+ StatusTgtErrorConfig = "ErrorConfig"
+ StatusTgtDisable = "Disable"
+ StatusTgtEnable = "Enable"
+)
+
+// TargetConfig config of a target / board
+type TargetConfig struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Type TargetType `json:"type"`
+ IP string `json:"ip"`
+ Status string `json:"status"`
+ Terms []TerminalConfig `json:"terms"`
+}
+
+/**
+ * Terminal
+ **/
+
+// TerminalType definition
+type TerminalType string
+
+const (
+ // TypeTermSSH ssh terminal type
+ TypeTermSSH = "ssh"
+
+ // StatusTermErrorConfig Configuration error
+ StatusTermErrorConfig = "ErrorConfig"
+ // StatusTermEnable Enable state
+ StatusTermEnable = "Enable"
+ // StatusTermOpen Open state
+ StatusTermOpen = "Open"
+ // StatusTermClose Close state
+ StatusTermClose = "Close"
+
+ // TerminalInEvent Event send in WS when characters are sent (stdin)
+ TerminalInEvent = "term:input"
+ // TerminalOutEvent Event send in WS when characters are received (stdout or stderr)
+ TerminalOutEvent = "term:output"
+ // TerminalExitEvent Event send in WS on terminal/console exit
+ TerminalExitEvent = "term:exit"
+)
+
+type (
+
+ // TerminalConfig config of a board terminal
+ TerminalConfig struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Type TerminalType `json:"type"`
+ User string `json:"user"`
+ Options []string `json:"options"`
+ Status string `json:"status"`
+ Cols uint16 `json:"cols"`
+ Rows uint16 `json:"rows"`
+ }
+
+ // TerminalInMsg Message used to received input characters (stdin)
+ TerminalInMsg struct {
+ TermID string `json:"termID"`
+ Timestamp string `json:"timestamp"`
+ Stdin string `json:"stdin"`
+ }
+
+ // TerminalOutMsg Message used to send output characters (stdout+stderr)
+ TerminalOutMsg struct {
+ TermID string `json:"termID"`
+ Timestamp string `json:"timestamp"`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+ }
+
+ // TerminalExitMsg Message sent on terminal/console exit
+ TerminalExitMsg struct {
+ TermID string `json:"termID"`
+ Timestamp string `json:"timestamp"`
+ Code int `json:"code"`
+ Error error `json:"error"`
+ }
+
+ // TerminalResizeArgs JSON parameters of /terminal/:tid/resize command
+ TerminalResizeArgs struct {
+ Cols uint16 `json:"cols"`
+ Rows uint16 `json:"rows"`
+ }
+
+ // TerminalSignalArgs JSON parameters of /terminal/:tid/signal command
+ TerminalSignalArgs struct {
+ Signal string `json:"signal" binding:"required"` // signal number
+ }
+)