/* * Copyright (C) 2018 "IoT.bzh" * Author Sebastien Douheret * * 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 ( "encoding/json" "fmt" "net/http" "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xaapiv1" common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git" "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 } // targetsEventsForwardInit Register events forwarder for targets func (s *APIService) targetsEventsForwardInit(svr *XdsServer) error { if !svr.Connected { return fmt.Errorf("Cannot register events: XDS Server %v not connected", svr.ID) } // Forward Target events from XDS-server to client if _, err := svr.EventOn(xsapiv1.EVTTargetAdd, xaapiv1.EVTTargetAdd, s._targetsEventCB); err != nil { s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetAdd, err) return err } if _, err := svr.EventOn(xsapiv1.EVTTargetRemove, xaapiv1.EVTTargetRemove, s._targetsEventCB); err != nil { s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetRemove, err) return err } if _, err := svr.EventOn(xsapiv1.EVTTargetStateChange, xaapiv1.EVTTargetStateChange, s._targetsEventCB); err != nil { s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetStateChange, err) return err } return nil } func (s *APIService) _targetsEventCB(privD interface{}, data interface{}) error { evt := xsapiv1.EventMsg{} d, err := json.Marshal(data) if err != nil { s.Log.Errorf("Cannot marshal XDS Server Target event err=%v, data=%v", err, data) return err } if err = json.Unmarshal(d, &evt); err != nil { s.Log.Errorf("Cannot unmarshal XDS Server Target event err=%v, d=%v", err, string(d)) return err } // assume that xsapiv1.TargetConfig == xaapiv1.TargetConfig target, err := evt.DecodeTargetEvent() if err != nil { s.Log.Errorf("Cannot decode XDS Server Target event: err=%v, data=%v", err, data) return err } evtName := privD.(string) if err := s.events.Emit(evtName, target, ""); err != nil { s.Log.Warningf("Cannot notify %s (from server): %v", evtName, err) return err } return nil } // terminalsEventsForwardInit Register events forwarder for terminals func (s *APIService) terminalsEventsForwardInit(svr *XdsServer) error { if !svr.Connected { return fmt.Errorf("Cannot register events: XDS Server %v not connected", svr.ID) } // Forward Terminal events from XDS-server to client if _, err := svr.EventOn(xsapiv1.EVTTargetTerminalAdd, xaapiv1.EVTTargetTerminalAdd, s._terminalsEventCB); err != nil { s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetTerminalAdd, err) return err } if _, err := svr.EventOn(xsapiv1.EVTTargetTerminalRemove, xaapiv1.EVTTargetTerminalRemove, s._terminalsEventCB); err != nil { s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetTerminalRemove, err) return err } if _, err := svr.EventOn(xsapiv1.EVTTargetTerminalStateChange, xaapiv1.EVTTargetTerminalStateChange, s._terminalsEventCB); err != nil { s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTTargetTerminalStateChange, err) return err } return nil } func (s *APIService) _terminalsEventCB(privD interface{}, data interface{}) error { evt := xsapiv1.EventMsg{} d, err := json.Marshal(data) if err != nil { s.Log.Errorf("Cannot marshal XDS Server Target event err=%v, data=%v", err, data) return err } if err = json.Unmarshal(d, &evt); err != nil { s.Log.Errorf("Cannot unmarshal XDS Server Target event err=%v, d=%v", err, string(d)) return err } // assume that xsapiv1.TargetConfig == xaapiv1.TargetConfig target, err := evt.DecodeTerminalEvent() if err != nil { s.Log.Errorf("Cannot decode XDS Server Target event: err=%v, data=%v", err, data) return err } evtName := privD.(string) if err := s.events.Emit(evtName, target, ""); err != nil { s.Log.Warningf("Cannot notify %s (from server): %v", evtName, err) return err } 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) }