Added target and terminal support.
[src/xds/xds-agent.git] / lib / agent / apiv1-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 agent
19
20 import (
21         "fmt"
22         "net/http"
23
24         "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent/lib/xaapiv1"
25         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
26         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
27         "github.com/franciscocpg/reflectme"
28         "github.com/gin-gonic/gin"
29         uuid "github.com/satori/go.uuid"
30 )
31
32 // targetsPassthroughInit Declare passthrough routes for targets
33 func (s *APIService) targetsPassthroughInit(svr *XdsServer) error {
34         svr.PassthroughGet("/targets")
35         svr.PassthroughGet("/targets/:id")
36         svr.PassthroughPost("/targets")
37         svr.PassthroughDelete("/targets/:id")
38
39         svr.PassthroughGet("/targets/:id/terminals")
40         svr.PassthroughGet("/targets/:id/terminals/:tid")
41         svr.PassthroughPost("/targets/:id/terminals")
42         svr.PassthroughPut("/targets/:id/terminals/:tid")
43         svr.PassthroughDelete("/targets/:id/terminals/:tid")
44
45         svr.apiRouter.POST("/targets/:id/terminals/:tid/open", s.TargetTerminalOpen)
46
47         svr.PassthroughPost("/targets/:id/terminals/:tid/close")
48         svr.PassthroughPost("/targets/:id/terminals/:tid/resize")
49         svr.PassthroughPost("/targets/:id/terminals/:tid/signal")
50         svr.PassthroughPost("/targets/:id/terminals/:tid/signal/:sig")
51
52         return nil
53 }
54
55 // GetServerFromTargetID Retrieve XDS Server definition from a target ID
56 func (s *APIService) GetServerFromTargetID(targetID, termID string) (*XdsServer, string, error) {
57
58         // FIXME add cache (but take care to support partial term ID)
59         for _, svr := range s.xdsServers {
60                 term := xsapiv1.TerminalConfig{}
61                 if err := svr.CommandTgtTerminalGet(targetID, termID, &term); err == nil {
62                         return svr, term.ID, nil
63                 }
64         }
65         return nil, "", fmt.Errorf("Cannot identify XDS Server")
66 }
67
68 // TargetTerminalOpen Open a target terminal/console
69 func (s *APIService) TargetTerminalOpen(c *gin.Context) {
70
71         // First retrieve Server ID and send command to right server
72         targetID := c.Param("id")
73         svr, termID, err := s.GetServerFromTargetID(targetID, c.Param("tid"))
74         if err != nil {
75                 common.APIError(c, err.Error())
76                 return
77         }
78
79         // Retrieve session info
80         sess := s.sessions.Get(c)
81         if sess == nil {
82                 common.APIError(c, "Unknown sessions")
83                 return
84         }
85         sock := sess.IOSocket
86         if sock == nil {
87                 common.APIError(c, "Websocket not established")
88                 return
89         }
90
91         // Forward input events from client to XDSServer through WS
92         err = (*sock).On(xsapiv1.TerminalInEvent, func(stdin string) {
93                 s.LogSillyf("TARGET TERMINAL EVENT IN (%s) <<%v>>", xsapiv1.TerminalInEvent, stdin)
94                 svr.EventEmit(xaapiv1.TerminalInEvent, stdin)
95         })
96         if err != nil {
97                 msgErr := "Error while registering WS for " + xsapiv1.TerminalInEvent + " event"
98                 s.Log.Errorf(msgErr, ", err: %v", err)
99                 common.APIError(c, msgErr)
100                 return
101         }
102
103         // Forward output events from XDSServer to client through WS
104         var outFwdFuncID uuid.UUID
105         outFwdFunc := func(pData interface{}, evData interface{}) error {
106                 sid := pData.(string)
107                 // IO socket can be nil when disconnected
108                 so := s.sessions.IOSocketGet(sid)
109                 if so == nil {
110                         s.Log.Infof("%s not emitted: WS closed (sid:%s)", xaapiv1.TerminalOutEvent, sid)
111                         return nil
112                 }
113
114                 // Add sessionID to event Data
115                 reflectme.SetField(evData, "sessionID", sid)
116
117                 s.LogSillyf("TARGET TERMINAL EVENT OUT (%s) <<%v>>", xaapiv1.TerminalOutEvent, evData)
118
119                 // Forward event to Client/Dashboard
120                 (*so).Emit(xaapiv1.TerminalOutEvent, evData)
121                 return nil
122         }
123         outFwdFuncID, err = svr.EventOn(xsapiv1.TerminalOutEvent, sess.ID, outFwdFunc)
124         if err != nil {
125                 common.APIError(c, err.Error())
126                 return
127         }
128
129         // Handle Exit event separately to cleanup registered listener
130         var exitFuncID uuid.UUID
131         exitFunc := func(privD interface{}, evData interface{}) error {
132                 evN := xaapiv1.TerminalExitEvent
133
134                 pData := privD.(map[string]string)
135                 sid := pData["sessID"]
136
137                 // Add sessionID to event Data
138                 reflectme.SetField(evData, "sessionID", sid)
139
140                 // IO socket can be nil when disconnected
141                 so := s.sessions.IOSocketGet(sid)
142                 if so != nil {
143                         (*so).Emit(evN, evData)
144                 } else {
145                         s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
146                 }
147
148                 // cleanup listener
149                 svr.EventOff(xaapiv1.TerminalOutEvent, outFwdFuncID)
150                 svr.EventOff(evN, exitFuncID)
151
152                 return nil
153         }
154
155         privData := map[string]string{
156                 "sessID": sess.ID,
157         }
158         exitFuncID, err = svr.EventOn(xaapiv1.TerminalExitEvent, privData, exitFunc)
159         if err != nil {
160                 common.APIError(c, err.Error())
161                 return
162         }
163
164         // Forward back command to right server
165         res := xsapiv1.TerminalConfig{}
166         if err := svr.CommandTgtTerminalOpen(targetID, termID, &res); err != nil {
167                 common.APIError(c, err.Error())
168                 return
169         }
170
171         c.JSON(http.StatusOK, res)
172 }