2f38b63db58b75bdda76312e172a8398f4fd16ba
[src/xds/xds-agent.git] / lib / agent / apiv1-exec.go
1 /*
2  * Copyright (C) 2017 "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         "net/http"
22
23         "github.com/franciscocpg/reflectme"
24         "github.com/gin-gonic/gin"
25         "github.com/iotbzh/xds-agent/lib/xaapiv1"
26         common "github.com/iotbzh/xds-common/golib"
27         "github.com/iotbzh/xds-server/lib/xsapiv1"
28         uuid "github.com/satori/go.uuid"
29 )
30
31 // ExecCmd executes remotely a command
32 func (s *APIService) execCmd(c *gin.Context) {
33
34         args := xaapiv1.ExecArgs{}
35         if err := c.BindJSON(&args); err != nil {
36                 s.Log.Warningf("/exec invalid args, err=%v", err)
37                 common.APIError(c, "Invalid arguments")
38                 return
39         }
40
41         // First get Project ID to retrieve Server ID and send command to right server
42         iid := c.Param("id")
43         if iid == "" {
44                 iid = args.ID
45         }
46         id, err := s.projects.ResolveID(iid)
47         if err != nil {
48                 common.APIError(c, err.Error())
49                 return
50         }
51         prj := s.projects.Get(id)
52         if prj == nil {
53                 common.APIError(c, "Unknown id")
54                 return
55         }
56
57         svr := (*prj).GetServer()
58         if svr == nil {
59                 common.APIError(c, "Cannot identify XDS Server")
60                 return
61         }
62
63         // Retrieve session info
64         sess := s.sessions.Get(c)
65         if sess == nil {
66                 common.APIError(c, "Unknown sessions")
67                 return
68         }
69         sock := sess.IOSocket
70         if sock == nil {
71                 common.APIError(c, "Websocket not established")
72                 return
73         }
74
75         // Forward input events from client to XDSServer through WS
76         // TODO use XDSServer events names definition
77         evtInList := []string{
78                 xaapiv1.ExecInEvent,
79                 xaapiv1.ExecInferiorInEvent,
80         }
81         for _, evName := range evtInList {
82                 evN := evName
83                 err := (*sock).On(evN, func(stdin string) {
84                         if s.LogLevelSilly {
85                                 s.Log.Debugf("EXEC EVENT IN (%s) <<%v>>", evN, stdin)
86                         }
87                         svr.EventEmit(evN, stdin)
88                 })
89                 if err != nil {
90                         msgErr := "Error while registering WS for " + evN + " event"
91                         s.Log.Errorf(msgErr, ", err: %v", err)
92                         common.APIError(c, msgErr)
93                         return
94                 }
95         }
96
97         // Forward output events from XDSServer to client through WS
98         // TODO use XDSServer events names definition
99         var fwdFuncID []uuid.UUID
100         evtOutList := []string{
101                 xaapiv1.ExecOutEvent,
102                 xaapiv1.ExecInferiorOutEvent,
103         }
104         for _, evName := range evtOutList {
105                 evN := evName
106                 fwdFunc := func(pData interface{}, evData interface{}) error {
107                         sid := pData.(string)
108                         // IO socket can be nil when disconnected
109                         so := s.sessions.IOSocketGet(sid)
110                         if so == nil {
111                                 s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
112                                 return nil
113                         }
114
115                         // Add sessionID to event Data
116                         reflectme.SetField(evData, "sessionID", sid)
117
118                         if s.LogLevelSilly {
119                                 s.Log.Debugf("EXEC EVENT OUT (%s) <<%v>>", evN, evData)
120                         }
121
122                         // Forward event to Client/Dashboard
123                         (*so).Emit(evN, evData)
124                         return nil
125                 }
126                 id, err := svr.EventOn(evN, sess.ID, fwdFunc)
127                 if err != nil {
128                         common.APIError(c, err.Error())
129                         return
130                 }
131                 fwdFuncID = append(fwdFuncID, id)
132         }
133
134         // Handle Exit event separately to cleanup registered listener
135         var exitFuncID uuid.UUID
136         exitFunc := func(privD interface{}, evData interface{}) error {
137                 evN := xaapiv1.ExecExitEvent
138
139                 pData := privD.(map[string]string)
140                 sid := pData["sessID"]
141                 prjID := pData["prjID"]
142
143                 // Add sessionID to event Data
144                 reflectme.SetField(evData, "sessionID", sid)
145
146                 // IO socket can be nil when disconnected
147                 so := s.sessions.IOSocketGet(sid)
148                 if so != nil {
149                         (*so).Emit(evN, evData)
150                 } else {
151                         s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
152                 }
153
154                 prj := s.projects.Get(prjID)
155                 if prj != nil {
156                         evD := evData.(map[string]interface{})
157                         cmdIDData, cmdIDExist := evD["cmdID"]
158                         svr := (*prj).GetServer()
159                         if svr != nil && cmdIDExist {
160                                 svr.CommandDelete(cmdIDData.(string))
161                         } else {
162                                 s.Log.Infof("%s: cannot retrieve server for sid=%s, prjID=%s, evD=%v", evN, sid, prjID, evD)
163                         }
164                 } else {
165                         s.Log.Infof("%s: cannot retrieve project for sid=%s, prjID=%s", evN, sid, prjID)
166                 }
167
168                 // cleanup listener
169                 for i, evName := range evtOutList {
170                         svr.EventOff(evName, fwdFuncID[i])
171                 }
172                 svr.EventOff(evN, exitFuncID)
173
174                 return nil
175         }
176
177         prjCfg := (*prj).GetProject()
178         privData := map[string]string{"sessID": sess.ID, "prjID": prjCfg.ID}
179         exitFuncID, err = svr.EventOn(xaapiv1.ExecExitEvent, privData, exitFunc)
180         if err != nil {
181                 common.APIError(c, err.Error())
182                 return
183         }
184
185         // Forward back command to right server
186         res := xsapiv1.ExecResult{}
187         xsArgs := &xsapiv1.ExecArgs{
188                 ID:              args.ID,
189                 SdkID:           args.SdkID,
190                 CmdID:           args.CmdID,
191                 Cmd:             args.Cmd,
192                 Args:            args.Args,
193                 Env:             args.Env,
194                 RPath:           args.RPath,
195                 TTY:             args.TTY,
196                 TTYGdbserverFix: args.TTYGdbserverFix,
197                 ExitImmediate:   args.ExitImmediate,
198                 CmdTimeout:      args.CmdTimeout,
199         }
200         if err := svr.CommandExec(xsArgs, &res); err != nil {
201                 common.APIError(c, err.Error())
202                 return
203         }
204
205         // Add command to running commands list
206         if err := svr.CommandAdd(res.CmdID, xsArgs); err != nil {
207                 common.APIError(c, err.Error())
208                 return
209         }
210
211         c.JSON(http.StatusOK, xaapiv1.ExecResult{Status: res.Status, CmdID: res.CmdID})
212 }
213
214 // execSignalCmd executes remotely the signal command
215 func (s *APIService) execSignalCmd(c *gin.Context) {
216
217         args := xaapiv1.ExecSignalArgs{}
218         if err := c.BindJSON(&args); err != nil {
219                 s.Log.Warningf("/signal invalid args, err=%v", err)
220                 common.APIError(c, "Invalid arguments")
221                 return
222         }
223
224         // Retrieve on which xds-server the command is running
225         var svr *XdsServer
226         var dataCmd interface{}
227         for _, svr = range s.xdsServers {
228                 dataCmd = svr.CommandGet(args.CmdID)
229                 if dataCmd != nil {
230                         break
231                 }
232         }
233         if dataCmd == nil {
234                 common.APIError(c, "Cannot retrieve XDS Server for this cmdID")
235                 return
236         }
237
238         // Forward back command to right server
239         res := xsapiv1.ExecSigResult{}
240         xsArgs := &xsapiv1.ExecSignalArgs{
241                 CmdID:  args.CmdID,
242                 Signal: args.Signal,
243         }
244         if err := svr.CommandSignal(xsArgs, &res); err != nil {
245                 common.APIError(c, err.Error())
246                 return
247         }
248
249         c.JSON(http.StatusOK, xaapiv1.ExecSignalResult{Status: res.Status, CmdID: res.CmdID})
250 }