summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 18:50:31 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 19:15:02 +0100
commitbefb10519daefcbee85950a4e574678fe4b7f402 (patch)
treeb4f95889cfacbdf9909c3ccd11dc1f0f8bedb417
parent38e0baedab9da4516cdc97b526392cbc2c7da67e (diff)
Added target and terminal support.v1.1.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--.vscode/settings.json3
-rw-r--r--glide.yaml4
-rw-r--r--lib/agent/apiv1-browse.go2
-rw-r--r--lib/agent/apiv1-config.go2
-rw-r--r--lib/agent/apiv1-targets.go172
-rw-r--r--lib/agent/apiv1.go8
-rw-r--r--lib/agent/webserver.go12
-rw-r--r--lib/agent/xdsserver.go69
-rw-r--r--lib/xaapiv1/targets.go124
9 files changed, 382 insertions, 14 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 2e081d9..47bcd75 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -90,7 +90,8 @@
"Sillyf",
"tabindex",
"EVTSDK",
- "gerrit"
+ "gerrit",
+ "tgts"
],
// codelyzer
"tslint.rulesDirectory": "./webapp/node_modules/codelyzer",
diff --git a/glide.yaml b/glide.yaml
index e7c6bf0..8ddc3e1 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -22,11 +22,11 @@ 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
- package: gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git
- version: ~1.0.0
+ version: ~1.1.0
subpackages:
- lib/xsapiv1
- package: github.com/franciscocpg/reflectme
diff --git a/lib/agent/apiv1-browse.go b/lib/agent/apiv1-browse.go
index dcfe79b..f1b25e8 100644
--- a/lib/agent/apiv1-browse.go
+++ b/lib/agent/apiv1-browse.go
@@ -37,7 +37,7 @@ func (s *APIService) browseFS(c *gin.Context) {
response := apiDirectory{
Dir: []directory{
- directory{Name: "TODO SEB"},
+ directory{Name: "TODO"},
},
}
diff --git a/lib/agent/apiv1-config.go b/lib/agent/apiv1-config.go
index b24dc21..cfecd82 100644
--- a/lib/agent/apiv1-config.go
+++ b/lib/agent/apiv1-config.go
@@ -66,7 +66,7 @@ func (s *APIService) setConfig(c *gin.Context) {
}
}
- // Add new XDS Server
+ // Add new / unconnected XDS Server
for _, svr := range cfgArg.Servers {
if svr.Connected && svr.ID != "" {
continue
diff --git a/lib/agent/apiv1-targets.go b/lib/agent/apiv1-targets.go
new file mode 100644
index 0000000..5a7862a
--- /dev/null
+++ b/lib/agent/apiv1-targets.go
@@ -0,0 +1,172 @@
+/*
+ * 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 agent
+
+import (
+ "fmt"
+ "net/http"
+
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent/lib/xaapiv1"
+ common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
+ "github.com/franciscocpg/reflectme"
+ "github.com/gin-gonic/gin"
+ uuid "github.com/satori/go.uuid"
+)
+
+// targetsPassthroughInit Declare passthrough routes for targets
+func (s *APIService) targetsPassthroughInit(svr *XdsServer) error {
+ svr.PassthroughGet("/targets")
+ svr.PassthroughGet("/targets/:id")
+ svr.PassthroughPost("/targets")
+ svr.PassthroughDelete("/targets/:id")
+
+ svr.PassthroughGet("/targets/:id/terminals")
+ svr.PassthroughGet("/targets/:id/terminals/:tid")
+ svr.PassthroughPost("/targets/:id/terminals")
+ svr.PassthroughPut("/targets/:id/terminals/:tid")
+ svr.PassthroughDelete("/targets/:id/terminals/:tid")
+
+ svr.apiRouter.POST("/targets/:id/terminals/:tid/open", s.TargetTerminalOpen)
+
+ svr.PassthroughPost("/targets/:id/terminals/:tid/close")
+ svr.PassthroughPost("/targets/:id/terminals/:tid/resize")
+ svr.PassthroughPost("/targets/:id/terminals/:tid/signal")
+ svr.PassthroughPost("/targets/:id/terminals/:tid/signal/:sig")
+
+ return nil
+}
+
+// GetServerFromTargetID Retrieve XDS Server definition from a target ID
+func (s *APIService) GetServerFromTargetID(targetID, termID string) (*XdsServer, string, error) {
+
+ // FIXME add cache (but take care to support partial term ID)
+ for _, svr := range s.xdsServers {
+ term := xsapiv1.TerminalConfig{}
+ if err := svr.CommandTgtTerminalGet(targetID, termID, &term); err == nil {
+ return svr, term.ID, nil
+ }
+ }
+ return nil, "", fmt.Errorf("Cannot identify XDS Server")
+}
+
+// TargetTerminalOpen Open a target terminal/console
+func (s *APIService) TargetTerminalOpen(c *gin.Context) {
+
+ // First retrieve Server ID and send command to right server
+ targetID := c.Param("id")
+ svr, termID, err := s.GetServerFromTargetID(targetID, c.Param("tid"))
+ 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
+ }
+
+ // Forward input events from client to XDSServer through WS
+ err = (*sock).On(xsapiv1.TerminalInEvent, func(stdin string) {
+ s.LogSillyf("TARGET TERMINAL EVENT IN (%s) <<%v>>", xsapiv1.TerminalInEvent, stdin)
+ svr.EventEmit(xaapiv1.TerminalInEvent, stdin)
+ })
+ if err != nil {
+ msgErr := "Error while registering WS for " + xsapiv1.TerminalInEvent + " event"
+ s.Log.Errorf(msgErr, ", err: %v", err)
+ common.APIError(c, msgErr)
+ return
+ }
+
+ // Forward output events from XDSServer to client through WS
+ var outFwdFuncID uuid.UUID
+ outFwdFunc := func(pData interface{}, evData interface{}) error {
+ sid := pData.(string)
+ // IO socket can be nil when disconnected
+ so := s.sessions.IOSocketGet(sid)
+ if so == nil {
+ s.Log.Infof("%s not emitted: WS closed (sid:%s)", xaapiv1.TerminalOutEvent, sid)
+ return nil
+ }
+
+ // Add sessionID to event Data
+ reflectme.SetField(evData, "sessionID", sid)
+
+ s.LogSillyf("TARGET TERMINAL EVENT OUT (%s) <<%v>>", xaapiv1.TerminalOutEvent, evData)
+
+ // Forward event to Client/Dashboard
+ (*so).Emit(xaapiv1.TerminalOutEvent, evData)
+ return nil
+ }
+ outFwdFuncID, err = svr.EventOn(xsapiv1.TerminalOutEvent, sess.ID, outFwdFunc)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ // Handle Exit event separately to cleanup registered listener
+ var exitFuncID uuid.UUID
+ exitFunc := func(privD interface{}, evData interface{}) error {
+ evN := xaapiv1.TerminalExitEvent
+
+ pData := privD.(map[string]string)
+ sid := pData["sessID"]
+
+ // Add sessionID to event Data
+ reflectme.SetField(evData, "sessionID", sid)
+
+ // IO socket can be nil when disconnected
+ so := s.sessions.IOSocketGet(sid)
+ if so != nil {
+ (*so).Emit(evN, evData)
+ } else {
+ s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
+ }
+
+ // cleanup listener
+ svr.EventOff(xaapiv1.TerminalOutEvent, outFwdFuncID)
+ svr.EventOff(evN, exitFuncID)
+
+ return nil
+ }
+
+ privData := map[string]string{
+ "sessID": sess.ID,
+ }
+ exitFuncID, err = svr.EventOn(xaapiv1.TerminalExitEvent, privData, exitFunc)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ // Forward back command to right server
+ res := xsapiv1.TerminalConfig{}
+ if err := svr.CommandTgtTerminalOpen(targetID, termID, &res); err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, res)
+}
diff --git a/lib/agent/apiv1.go b/lib/agent/apiv1.go
index 1921f98..3ca84b9 100644
--- a/lib/agent/apiv1.go
+++ b/lib/agent/apiv1.go
@@ -49,7 +49,7 @@ func NewAPIV1(ctx *Context) *APIService {
s.apiRouter.GET("/config", s.getConfig)
s.apiRouter.POST("/config", s.setConfig)
- s.apiRouter.GET("/browse", s.browseFS)
+ // s.apiRouter.GET("/browse", s.browseFS)
s.apiRouter.GET("/projects", s.getProjects)
s.apiRouter.GET("/projects/:id", s.getProject)
@@ -118,6 +118,7 @@ func (s *APIService) AddXdsServer(cfg xdsconfig.XDSServerConf) (*XdsServer, erro
// Declare passthrough routes
s.sdksPassthroughInit(svr)
+ s.targetsPassthroughInit(svr)
// Register callback on Connection
svr.ConnectOn(func(server *XdsServer) error {
@@ -125,9 +126,9 @@ func (s *APIService) AddXdsServer(cfg xdsconfig.XDSServerConf) (*XdsServer, erro
// Add server to list
s.xdsServers[server.ID] = svr
- // Register event forwarder
+ // Register events forwarder
if err := s.sdksEventsForwardInit(server); err != nil {
- s.Log.Errorf("XDS Server %v - sdk event forwarding error: %v", server.ID, err)
+ s.Log.Errorf("XDS Server %v - sdk events forwarding error: %v", server.ID, err)
}
// Load projects
@@ -164,7 +165,6 @@ func (s *APIService) DelXdsServer(id string) error {
s.xdsServers[id].Close()
return nil
}
-}
// UpdateXdsServer Update XDS Server configuration settings
func (s *APIService) UpdateXdsServer(cfg xaapiv1.ServerCfg) error {
diff --git a/lib/agent/webserver.go b/lib/agent/webserver.go
index 2f24c6c..4deb738 100644
--- a/lib/agent/webserver.go
+++ b/lib/agent/webserver.go
@@ -246,13 +246,15 @@ func (s *WebServer) socketHandler(c *gin.Context) {
}
s.sIOServer.On("connection", func(so socketio.Socket) {
- s.Log.Debugf("WS Connected (WSID=%s, SID=%s)", so.Id(), sess.ID)
- s.sessions.UpdateIOSocket(sess.ID, &so)
+ sessID := sess.ID
+
+ s.Log.Debugf("WS Connected (sessID=%s, WS SID=%s)", sessID, so.Id())
+ s.sessions.UpdateIOSocket(sessID, &so)
so.On("disconnection", func() {
- s.Log.Debugf("WS disconnected (WSID=%s, SID=%s)", so.Id(), sess.ID)
- s.events.UnRegister(xaapiv1.EVTAll, sess.ID)
- s.sessions.UpdateIOSocket(sess.ID, nil)
+ s.Log.Debugf("WS disconnected (sessID=%s, WS SID=%s)", sessID, so.Id())
+ s.events.UnRegister(xaapiv1.EVTAll, sessID)
+ s.sessions.UpdateIOSocket(sessID, nil)
})
})
diff --git a/lib/agent/xdsserver.go b/lib/agent/xdsserver.go
index 1c715f6..c08bfb1 100644
--- a/lib/agent/xdsserver.go
+++ b/lib/agent/xdsserver.go
@@ -208,6 +208,22 @@ func (xs *XdsServer) CommandSignal(args *xsapiv1.ExecSignalArgs, res *xsapiv1.Ex
return xs.client.Post("/signal", args, res)
}
+// CommandTgtTerminalGet Send GET request to retrieve info of a target terminals
+func (xs *XdsServer) CommandTgtTerminalGet(targetID, termID string, res *xsapiv1.TerminalConfig) error {
+ return xs.client.Get("/targets/"+targetID+"/terminals/"+termID, res)
+}
+
+// CommandTgtTerminalOpen Send POST request to open a target terminal
+func (xs *XdsServer) CommandTgtTerminalOpen(targetID string, termID string, res *xsapiv1.TerminalConfig) error {
+ var empty interface{}
+ return xs.client.Post("/targets/"+targetID+"/terminals/"+termID+"/open", &empty, res)
+}
+
+// CommandTgtTerminalSignal Send POST request to send a signal to a target terminal
+func (xs *XdsServer) CommandTgtTerminalSignal(args *xsapiv1.TerminalSignalArgs, res *xsapiv1.TerminalConfig) error {
+ return xs.client.Post("/signal", args, res)
+}
+
// SetAPIRouterGroup .
func (xs *XdsServer) SetAPIRouterGroup(r *gin.RouterGroup) {
xs.apiRouter = r
@@ -290,6 +306,56 @@ func (xs *XdsServer) PassthroughPost(url string) {
})
}
+// PassthroughPut Used to declare a route that sends directly a PUT request to XDS Server
+func (xs *XdsServer) PassthroughPut(url string) {
+ if xs.apiRouter == nil {
+ xs.Log.Errorf("apiRouter not set !")
+ return
+ }
+
+ xs.apiRouter.PUT(url, func(c *gin.Context) {
+ var err error
+ var data interface{}
+
+ // Get raw body
+ body, err := c.GetRawData()
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ // Take care of param (eg. id in /projects/:id)
+ nURL := url
+ if strings.Contains(url, ":") {
+ nURL = strings.TrimPrefix(c.Request.URL.Path, xs.APIURL)
+ }
+
+ // Send Put request
+ response, err := xs.client.HTTPPutWithRes(nURL, string(body))
+ if err != nil {
+ goto httpError
+ }
+ if response.StatusCode != 200 {
+ err = fmt.Errorf(response.Status)
+ return
+ }
+ err = json.Unmarshal(xs.client.ResponseToBArray(response), &data)
+ if err != nil {
+ goto httpError
+ }
+
+ c.JSON(http.StatusOK, data)
+ return
+
+ /* Handle error case */
+ httpError:
+ if strings.Contains(err.Error(), "connection refused") {
+ xs._Disconnected()
+ }
+ common.APIError(c, err.Error())
+ })
+}
+
// PassthroughDelete Used to declare a route that sends directly a Delete request to XDS Server
func (xs *XdsServer) PassthroughDelete(url string) {
if xs.apiRouter == nil {
@@ -660,6 +726,9 @@ func (xs *XdsServer) _SocketConnect() error {
// _Disconnected Set XDS Server as disconnected
func (xs *XdsServer) _Disconnected() error {
// Clear all register events as socket is closed
+ xs.sockEventsLock.Lock()
+ defer xs.sockEventsLock.Unlock()
+
for k := range xs.sockEvents {
delete(xs.sockEvents, k)
}
diff --git a/lib/xaapiv1/targets.go b/lib/xaapiv1/targets.go
new file mode 100644
index 0000000..9cea6ff
--- /dev/null
+++ b/lib/xaapiv1/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 xaapiv1
+
+/**
+ * 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
+ }
+)