Use go module as dependency tool instead of glide
[src/xds/xds-agent.git] / lib / agent / xdsserver.go
1 /*
2  * Copyright (C) 2017-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         "encoding/json"
22         "fmt"
23         "io"
24         "net/http"
25         "strings"
26         "sync"
27         "time"
28
29         "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xaapiv1"
30         "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xdsconfig"
31         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
32         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
33         "github.com/gin-gonic/gin"
34         uuid "github.com/satori/go.uuid"
35         sio_client "github.com/sebd71/go-socket.io-client"
36 )
37
38 // XdsServer .
39 type XdsServer struct {
40         *Context
41         ID           string
42         URLIndex     string
43         BaseURL      string
44         APIURL       string
45         PartialURL   string
46         ConnRetry    int
47         Connected    bool
48         Disabled     bool
49         ServerConfig *xsapiv1.APIConfig
50
51         // Events management
52         CBOnError      func(error)
53         CBOnDisconnect func(error)
54         sockEvents     map[string][]*caller
55         sockEventsLock *sync.Mutex
56
57         // Private fields
58         client      *common.HTTPClient
59         ioSock      *sio_client.Client
60         logOut      io.Writer
61         apiRouter   *gin.RouterGroup
62         cmdList     map[string]interface{}
63         cbOnConnect OnConnectedCB
64 }
65
66 // EventCB Event emitter callback
67 type EventCB func(privData interface{}, evtData interface{}) error
68
69 // OnConnectedCB connect callback
70 type OnConnectedCB func(svr *XdsServer) error
71
72 // caller Used to chain event listeners
73 type caller struct {
74         id          uuid.UUID
75         EventName   string
76         Func        EventCB
77         PrivateData interface{}
78 }
79
80 const _IDTempoPrefix = "tempo-"
81
82 // NewXdsServer creates an instance of XdsServer
83 func NewXdsServer(ctx *Context, conf xdsconfig.XDSServerConf) *XdsServer {
84         return &XdsServer{
85                 Context:    ctx,
86                 ID:         _IDTempoPrefix + uuid.NewV1().String(),
87                 URLIndex:   conf.URLIndex,
88                 BaseURL:    conf.URL,
89                 APIURL:     conf.APIBaseURL + conf.APIPartialURL,
90                 PartialURL: conf.APIPartialURL,
91                 ConnRetry:  conf.ConnRetry,
92                 Connected:  false,
93                 Disabled:   false,
94
95                 sockEvents:     make(map[string][]*caller),
96                 sockEventsLock: &sync.Mutex{},
97                 logOut:         ctx.Log.Out,
98                 cmdList:        make(map[string]interface{}),
99         }
100 }
101
102 // Close Free and close XDS Server connection
103 func (xs *XdsServer) Close() error {
104         err := xs._Disconnected()
105         xs.Disabled = true
106         return err
107 }
108
109 // Connect Establish HTTP connection with XDS Server
110 func (xs *XdsServer) Connect() error {
111         var err error
112         var retry int
113
114         xs.Disabled = false
115         xs.Connected = false
116
117         err = nil
118         for retry = xs.ConnRetry; retry > 0; retry-- {
119                 if err = xs._CreateConnectHTTP(); err == nil {
120                         break
121                 }
122                 if retry == xs.ConnRetry {
123                         // Notify only on the first conn error
124                         // doing that avoid 2 notifs (conn false; conn true) on startup
125                         xs._NotifyState()
126                 }
127                 xs.Log.Infof("Establishing connection to XDS Server (retry %d/%d)", retry, xs.ConnRetry)
128                 time.Sleep(time.Second)
129         }
130         if retry == 0 {
131                 // FIXME: re-use _Reconnect to wait longer in background
132                 return fmt.Errorf("Connection to XDS Server failure")
133         }
134         if err != nil {
135                 return err
136         }
137
138         // Check HTTP connection and establish WS connection
139         err = xs._Connect(false)
140
141         return err
142 }
143
144 // ConnectOn Register a callback on events reception
145 func (xs *XdsServer) ConnectOn(f OnConnectedCB) error {
146         xs.cbOnConnect = f
147         return nil
148 }
149
150 // IsTempoID returns true when server as a temporary id
151 func (xs *XdsServer) IsTempoID() bool {
152         return strings.HasPrefix(xs.ID, _IDTempoPrefix)
153 }
154
155 // SetLoggerOutput Set logger ou
156 func (xs *XdsServer) SetLoggerOutput(out io.Writer) {
157         xs.logOut = out
158 }
159
160 // GetConfig return the current server config
161 func (xs *XdsServer) GetConfig() xaapiv1.ServerCfg {
162         return xaapiv1.ServerCfg{
163                 ID:         xs.ID,
164                 URL:        xs.BaseURL,
165                 APIURL:     xs.APIURL,
166                 PartialURL: xs.PartialURL,
167                 ConnRetry:  xs.ConnRetry,
168                 Connected:  xs.Connected,
169                 Disabled:   xs.Disabled,
170         }
171 }
172
173 // SendCommand Send a command to XDS Server
174 func (xs *XdsServer) SendCommand(cmd string, body []byte, res interface{}) error {
175         url := cmd
176         if !strings.HasPrefix("/", cmd) {
177                 url = "/" + cmd
178         }
179         return xs.client.Post(url, string(body), res)
180 }
181
182 // GetVersion Send Get request to retrieve XDS Server version
183 func (xs *XdsServer) GetVersion(res interface{}) error {
184         return xs.client.Get("/version", &res)
185 }
186
187 // GetFolders Send GET request to get current folder configuration
188 func (xs *XdsServer) GetFolders(folders *[]xsapiv1.FolderConfig) error {
189         return xs.client.Get("/folders", folders)
190 }
191
192 // FolderAdd Send POST request to add a folder
193 func (xs *XdsServer) FolderAdd(fld *xsapiv1.FolderConfig, res interface{}) error {
194         err := xs.client.Post("/folders", fld, res)
195         if err != nil {
196                 return fmt.Errorf("FolderAdd error: %s", err.Error())
197         }
198         return err
199 }
200
201 // FolderDelete Send DELETE request to delete a folder
202 func (xs *XdsServer) FolderDelete(id string) error {
203         return xs.client.HTTPDelete("/folders/" + id)
204 }
205
206 // FolderSync Send POST request to force synchronization of a folder
207 func (xs *XdsServer) FolderSync(id string) error {
208         return xs.client.HTTPPost("/folders/sync/"+id, "")
209 }
210
211 // FolderUpdate Send PUT request to update a folder
212 func (xs *XdsServer) FolderUpdate(fld *xsapiv1.FolderConfig, resFld *xsapiv1.FolderConfig) error {
213         return xs.client.Put("/folders/"+fld.ID, fld, resFld)
214 }
215
216 // CommandExec Send POST request to execute a command
217 func (xs *XdsServer) CommandExec(args *xsapiv1.ExecArgs, res *xsapiv1.ExecResult) error {
218         return xs.client.Post("/exec", args, res)
219 }
220
221 // CommandSignal Send POST request to send a signal to a command
222 func (xs *XdsServer) CommandSignal(args *xsapiv1.ExecSignalArgs, res *xsapiv1.ExecSigResult) error {
223         return xs.client.Post("/signal", args, res)
224 }
225
226 // CommandTgtTerminalGet Send GET request to retrieve info of a target terminals
227 func (xs *XdsServer) CommandTgtTerminalGet(targetID, termID string, res *xsapiv1.TerminalConfig) error {
228         return xs.client.Get("/targets/"+targetID+"/terminals/"+termID, res)
229 }
230
231 // CommandTgtTerminalOpen Send POST request to open a target terminal
232 func (xs *XdsServer) CommandTgtTerminalOpen(targetID string, termID string, res *xsapiv1.TerminalConfig) error {
233         var empty interface{}
234         return xs.client.Post("/targets/"+targetID+"/terminals/"+termID+"/open", &empty, res)
235 }
236
237 // CommandTgtTerminalSignal Send POST request to send a signal to a target terminal
238 func (xs *XdsServer) CommandTgtTerminalSignal(args *xsapiv1.TerminalSignalArgs, res *xsapiv1.TerminalConfig) error {
239         return xs.client.Post("/signal", args, res)
240 }
241
242 // SetAPIRouterGroup .
243 func (xs *XdsServer) SetAPIRouterGroup(r *gin.RouterGroup) {
244         xs.apiRouter = r
245 }
246
247 // PassthroughGet Used to declare a route that sends directly a GET request to XDS Server
248 func (xs *XdsServer) PassthroughGet(url string) {
249         if xs.apiRouter == nil {
250                 xs.Log.Errorf("apiRouter not set !")
251                 return
252         }
253
254         xs.apiRouter.GET(url, func(c *gin.Context) {
255                 var data interface{}
256                 // Take care of param (eg. id in /projects/:id)
257                 nURL := url
258                 if strings.Contains(url, ":") {
259                         nURL = strings.TrimPrefix(c.Request.URL.Path, xs.APIURL)
260                 }
261                 // Send Get request
262                 if err := xs.client.Get(nURL, &data); err != nil {
263                         if strings.Contains(err.Error(), "connection refused") {
264                                 xs._Disconnected()
265                         }
266                         common.APIError(c, err.Error())
267                         return
268                 }
269
270                 c.JSON(http.StatusOK, data)
271         })
272 }
273
274 // PassthroughPost Used to declare a route that sends directly a POST request to XDS Server
275 func (xs *XdsServer) PassthroughPost(url string) {
276         if xs.apiRouter == nil {
277                 xs.Log.Errorf("apiRouter not set !")
278                 return
279         }
280
281         xs.apiRouter.POST(url, func(c *gin.Context) {
282                 var err error
283                 var data interface{}
284
285                 // Get raw body
286                 body, err := c.GetRawData()
287                 if err != nil {
288                         common.APIError(c, err.Error())
289                         return
290                 }
291
292                 // Take care of param (eg. id in /projects/:id)
293                 nURL := url
294                 if strings.Contains(url, ":") {
295                         nURL = strings.TrimPrefix(c.Request.URL.Path, xs.APIURL)
296                 }
297
298                 // Send Post request
299                 response, err := xs.client.HTTPPostWithRes(nURL, string(body))
300                 if err != nil {
301                         goto httpError
302                 }
303                 if response.StatusCode != 200 {
304                         err = fmt.Errorf(response.Status)
305                         return
306                 }
307                 err = json.Unmarshal(xs.client.ResponseToBArray(response), &data)
308                 if err != nil {
309                         goto httpError
310                 }
311
312                 c.JSON(http.StatusOK, data)
313                 return
314
315                 /* Handle error case */
316         httpError:
317                 if strings.Contains(err.Error(), "connection refused") {
318                         xs._Disconnected()
319                 }
320                 common.APIError(c, err.Error())
321                 return
322         })
323 }
324
325 // PassthroughPut Used to declare a route that sends directly a PUT request to XDS Server
326 func (xs *XdsServer) PassthroughPut(url string) {
327         if xs.apiRouter == nil {
328                 xs.Log.Errorf("apiRouter not set !")
329                 return
330         }
331
332         xs.apiRouter.PUT(url, func(c *gin.Context) {
333                 var err error
334                 var data interface{}
335
336                 // Get raw body
337                 body, err := c.GetRawData()
338                 if err != nil {
339                         common.APIError(c, err.Error())
340                         return
341                 }
342
343                 // Take care of param (eg. id in /projects/:id)
344                 nURL := url
345                 if strings.Contains(url, ":") {
346                         nURL = strings.TrimPrefix(c.Request.URL.Path, xs.APIURL)
347                 }
348
349                 // Send Put request
350                 response, err := xs.client.HTTPPutWithRes(nURL, string(body))
351                 if err != nil {
352                         goto httpError
353                 }
354                 if response.StatusCode != 200 {
355                         err = fmt.Errorf(response.Status)
356                         return
357                 }
358                 err = json.Unmarshal(xs.client.ResponseToBArray(response), &data)
359                 if err != nil {
360                         goto httpError
361                 }
362
363                 c.JSON(http.StatusOK, data)
364                 return
365
366                 /* Handle error case */
367         httpError:
368                 if strings.Contains(err.Error(), "connection refused") {
369                         xs._Disconnected()
370                 }
371                 common.APIError(c, err.Error())
372                 return
373         })
374 }
375
376 // PassthroughDelete Used to declare a route that sends directly a Delete request to XDS Server
377 func (xs *XdsServer) PassthroughDelete(url string) {
378         if xs.apiRouter == nil {
379                 xs.Log.Errorf("apiRouter not set !")
380                 return
381         }
382
383         xs.apiRouter.DELETE(url, func(c *gin.Context) {
384                 var err error
385                 var data interface{}
386
387                 // Take care of param (eg. id in /projects/:id)
388                 nURL := url
389                 if strings.Contains(url, ":") {
390                         nURL = strings.TrimPrefix(c.Request.URL.Path, xs.APIURL)
391                 }
392
393                 // Send Post request
394                 response, err := xs.client.HTTPDeleteWithRes(nURL)
395                 if err != nil {
396                         goto httpError
397                 }
398                 if response.StatusCode != 200 {
399                         err = fmt.Errorf(response.Status)
400                         return
401                 }
402                 err = json.Unmarshal(xs.client.ResponseToBArray(response), &data)
403                 if err != nil {
404                         goto httpError
405                 }
406
407                 c.JSON(http.StatusOK, data)
408                 return
409
410                 /* Handle error case */
411         httpError:
412                 if strings.Contains(err.Error(), "connection refused") {
413                         xs._Disconnected()
414                 }
415                 common.APIError(c, err.Error())
416                 return
417         })
418 }
419
420 // EventRegister Post a request to register to an XdsServer event
421 func (xs *XdsServer) EventRegister(evName string, filter string) error {
422         return xs.client.Post("/events/register",
423                 xsapiv1.EventRegisterArgs{
424                         Name:   evName,
425                         Filter: filter,
426                 },
427                 nil)
428 }
429
430 // EventEmit Emit a event to XDS Server through WS
431 func (xs *XdsServer) EventEmit(message string, args ...interface{}) error {
432         if xs.ioSock == nil {
433                 return fmt.Errorf("Io.Socket not initialized")
434         }
435
436         return xs.ioSock.Emit(message, args...)
437 }
438
439 // EventOn Register a callback on events reception
440 func (xs *XdsServer) EventOn(evName string, privData interface{}, f EventCB) (uuid.UUID, error) {
441         if xs.ioSock == nil {
442                 return uuid.Nil, fmt.Errorf("Io.Socket not initialized")
443         }
444
445         xs.sockEventsLock.Lock()
446         defer xs.sockEventsLock.Unlock()
447
448         if _, exist := xs.sockEvents[evName]; !exist {
449                 // Register listener only the first time
450                 evn := evName
451
452                 err := xs.ioSock.On(evn, func(data interface{}) error {
453                         xs.sockEventsLock.Lock()
454                         sEvts := make([]*caller, len(xs.sockEvents[evn]))
455                         copy(sEvts, xs.sockEvents[evn])
456                         xs.sockEventsLock.Unlock()
457                         for _, c := range sEvts {
458                                 c.Func(c.PrivateData, data)
459                         }
460                         return nil
461                 })
462                 if err != nil {
463                         return uuid.Nil, err
464                 }
465         }
466
467         c := &caller{
468                 id:          uuid.NewV1(),
469                 EventName:   evName,
470                 Func:        f,
471                 PrivateData: privData,
472         }
473
474         xs.sockEvents[evName] = append(xs.sockEvents[evName], c)
475         xs.LogSillyf("XS EventOn: sockEvents[\"%s\"]: len %d", evName, len(xs.sockEvents[evName]))
476         return c.id, nil
477 }
478
479 // EventOff Un-register a (or all) callbacks associated to an event
480 func (xs *XdsServer) EventOff(evName string, id uuid.UUID) error {
481         xs.sockEventsLock.Lock()
482         defer xs.sockEventsLock.Unlock()
483         if _, exist := xs.sockEvents[evName]; exist {
484                 if id == uuid.Nil {
485                         // Un-register all
486                         xs.sockEvents[evName] = []*caller{}
487                 } else {
488                         // Un-register only the specified callback
489                         for i, ff := range xs.sockEvents[evName] {
490                                 if uuid.Equal(ff.id, id) {
491                                         xs.sockEvents[evName] = append(xs.sockEvents[evName][:i], xs.sockEvents[evName][i+1:]...)
492                                         break
493                                 }
494                         }
495                 }
496         }
497         xs.LogSillyf("XS EventOff: sockEvents[\"%s\"]: len %d", evName, len(xs.sockEvents[evName]))
498         return nil
499 }
500
501 // ProjectToFolder Convert Project structure to Folder structure
502 func (xs *XdsServer) ProjectToFolder(pPrj xaapiv1.ProjectConfig) *xsapiv1.FolderConfig {
503         stID := ""
504         if pPrj.Type == xsapiv1.TypeCloudSync {
505                 stID, _ = xs.SThg.IDGet()
506         }
507         // TODO: limit ClientData size and gzip it (see https://golang.org/pkg/compress/gzip/)
508         fPrj := xsapiv1.FolderConfig{
509                 ID:         pPrj.ID,
510                 Label:      pPrj.Label,
511                 ClientPath: pPrj.ClientPath,
512                 Type:       xsapiv1.FolderType(pPrj.Type),
513                 Status:     pPrj.Status,
514                 IsInSync:   pPrj.IsInSync,
515                 DefaultSdk: pPrj.DefaultSdk,
516                 ClientData: pPrj.ClientData,
517                 DataPathMap: xsapiv1.PathMapConfig{
518                         ServerPath: pPrj.ServerPath,
519                 },
520                 DataCloudSync: xsapiv1.CloudSyncConfig{
521                         SyncThingID:   stID,
522                         STLocIsInSync: pPrj.IsInSync,
523                         STLocStatus:   pPrj.Status,
524                         STSvrIsInSync: pPrj.IsInSync,
525                         STSvrStatus:   pPrj.Status,
526                 },
527         }
528
529         return &fPrj
530 }
531
532 // FolderToProject Convert Folder structure to Project structure
533 func (xs *XdsServer) FolderToProject(fPrj xsapiv1.FolderConfig) xaapiv1.ProjectConfig {
534         inSync := fPrj.IsInSync
535         sts := fPrj.Status
536
537         if fPrj.Type == xsapiv1.TypeCloudSync {
538                 inSync = fPrj.DataCloudSync.STSvrIsInSync && fPrj.DataCloudSync.STLocIsInSync
539
540                 sts = fPrj.DataCloudSync.STSvrStatus
541                 switch fPrj.DataCloudSync.STLocStatus {
542                 case xaapiv1.StatusErrorConfig, xaapiv1.StatusDisable, xaapiv1.StatusPause:
543                         sts = fPrj.DataCloudSync.STLocStatus
544                         break
545                 case xaapiv1.StatusSyncing:
546                         if sts != xaapiv1.StatusErrorConfig && sts != xaapiv1.StatusDisable && sts != xaapiv1.StatusPause {
547                                 sts = xaapiv1.StatusSyncing
548                         }
549                         break
550                 case xaapiv1.StatusEnable:
551                         // keep STSvrStatus
552                         break
553                 }
554         }
555
556         pPrj := xaapiv1.ProjectConfig{
557                 ID:         fPrj.ID,
558                 ServerID:   xs.ID,
559                 Label:      fPrj.Label,
560                 ClientPath: fPrj.ClientPath,
561                 ServerPath: fPrj.DataPathMap.ServerPath,
562                 Type:       xaapiv1.ProjectType(fPrj.Type),
563                 Status:     sts,
564                 IsInSync:   inSync,
565                 DefaultSdk: fPrj.DefaultSdk,
566                 ClientData: fPrj.ClientData,
567         }
568         return pPrj
569 }
570
571 // CommandAdd Add a new command to the list of running commands
572 func (xs *XdsServer) CommandAdd(cmdID string, data interface{}) error {
573         if xs.CommandGet(cmdID) != nil {
574                 return fmt.Errorf("command id already exist")
575         }
576         xs.cmdList[cmdID] = data
577         return nil
578 }
579
580 // CommandDelete Delete a command from the command list
581 func (xs *XdsServer) CommandDelete(cmdID string) error {
582         if xs.CommandGet(cmdID) == nil {
583                 return fmt.Errorf("unknown command id")
584         }
585         delete(xs.cmdList, cmdID)
586         return nil
587 }
588
589 // CommandGet Retrieve a command data
590 func (xs *XdsServer) CommandGet(cmdID string) interface{} {
591         d, exist := xs.cmdList[cmdID]
592         if exist {
593                 return d
594         }
595         return nil
596 }
597
598 /***
599 ** Private functions
600 ***/
601
602 // Create HTTP client
603 func (xs *XdsServer) _CreateConnectHTTP() error {
604         var err error
605         xs.client, err = common.HTTPNewClient(xs.BaseURL,
606                 common.HTTPClientConfig{
607                         URLPrefix:           "/api/v1",
608                         HeaderClientKeyName: "Xds-Sid",
609                         CsrfDisable:         true,
610                         LogOut:              xs.logOut,
611                         LogPrefix:           "XDSSERVER: ",
612                         LogLevel:            common.HTTPLogLevelWarning,
613                 })
614
615         xs.client.SetLogLevel(xs.Log.Level.String())
616
617         if err != nil {
618                 msg := ": " + err.Error()
619                 if strings.Contains(err.Error(), "connection refused") {
620                         msg = fmt.Sprintf("(url: %s)", xs.BaseURL)
621                 }
622                 return fmt.Errorf("ERROR: cannot connect to XDS Server %s", msg)
623         }
624         if xs.client == nil {
625                 return fmt.Errorf("ERROR: cannot connect to XDS Server (null client)")
626         }
627
628         return nil
629 }
630
631 // _Reconnect Re-established connection
632 func (xs *XdsServer) _Reconnect() error {
633
634         // Note that ConnectOn callback will be called (see apiv1.go file)
635         err := xs._Connect(true)
636
637         return err
638 }
639
640 // _Connect Established HTTP and WS connection and retrieve XDSServer config
641 func (xs *XdsServer) _Connect(reConn bool) error {
642
643         xdsCfg := xsapiv1.APIConfig{}
644         if err := xs.client.Get("/config", &xdsCfg); err != nil {
645                 xs.Connected = false
646                 if !reConn {
647                         xs._NotifyState()
648                 }
649                 return err
650         }
651
652         if reConn && xs.ID != xdsCfg.ServerUID {
653                 xs.Log.Warningf("Reconnected to server but ID differs: old=%s, new=%s", xs.ID, xdsCfg.ServerUID)
654         }
655
656         // Update local XDS config
657         xs.ID = xdsCfg.ServerUID
658         xs.ServerConfig = &xdsCfg
659
660         // Establish WS connection and register listen
661         if err := xs._SocketConnect(); err != nil {
662                 xs._Disconnected()
663                 return err
664         }
665
666         xs.Connected = true
667
668         // Call OnConnect callback
669         if xs.cbOnConnect != nil {
670                 xs.cbOnConnect(xs)
671         }
672
673         xs._NotifyState()
674         return nil
675 }
676
677 // _SocketConnect Create WebSocket (io.socket) connection
678 func (xs *XdsServer) _SocketConnect() error {
679
680         xs.Log.Infof("Connecting IO.socket for server %s (url %s)", xs.ID, xs.BaseURL)
681
682         opts := &sio_client.Options{
683                 Transport: "websocket",
684                 Header:    make(map[string][]string),
685         }
686         opts.Header["XDS-SID"] = []string{xs.client.GetClientID()}
687
688         iosk, err := sio_client.NewClient(xs.BaseURL, opts)
689         if err != nil {
690                 return fmt.Errorf("IO.socket connection error for server %s: %v", xs.ID, err)
691         }
692         xs.ioSock = iosk
693
694         // Register some listeners
695
696         iosk.On("error", func(err error) {
697                 xs.Log.Infof("IO.socket Error server %s; err: %v", xs.ID, err)
698                 if xs.CBOnError != nil {
699                         xs.CBOnError(err)
700                 }
701         })
702
703         iosk.On("disconnection", func(err error) {
704                 xs.Log.Infof("IO.socket disconnection server %s (APIURL %s)", xs.ID, xs.APIURL)
705                 if xs.CBOnDisconnect != nil {
706                         xs.CBOnDisconnect(err)
707                 }
708                 xs._Disconnected()
709
710                 // Try to reconnect during 15min (or at least while not disabled)
711                 go func() {
712                         count := 0
713                         waitTime := 1
714                         for !xs.Disabled && !xs.Connected {
715                                 count++
716                                 if count%60 == 0 {
717                                         waitTime *= 5
718                                 }
719                                 if waitTime > 15*60 {
720                                         xs.Log.Infof("Stop reconnection to server url=%s id=%s !", xs.BaseURL, xs.ID)
721                                         return
722                                 }
723                                 time.Sleep(time.Second * time.Duration(waitTime))
724                                 xs.Log.Infof("Try to reconnect to server %s (%d)", xs.BaseURL, count)
725
726                                 err := xs._Reconnect()
727                                 if err != nil &&
728                                         !(strings.Contains(err.Error(), "dial tcp") && strings.Contains(err.Error(), "connection refused")) {
729                                         xs.Log.Errorf("ERROR while reconnecting: %v", err.Error())
730                                 }
731
732                         }
733                 }()
734         })
735
736         // XXX - There is no connection event generated so, just consider that
737         // we are connected when NewClient return successfully
738         /* iosk.On("connection", func() { ... }) */
739         xs.Log.Infof("IO.socket connected server url=%s id=%s", xs.BaseURL, xs.ID)
740
741         return nil
742 }
743
744 // _Disconnected Set XDS Server as disconnected
745 func (xs *XdsServer) _Disconnected() error {
746         // Clear all register events as socket is closed
747         xs.sockEventsLock.Lock()
748         defer xs.sockEventsLock.Unlock()
749
750         for k := range xs.sockEvents {
751                 delete(xs.sockEvents, k)
752         }
753         xs.Connected = false
754         xs.ioSock = nil
755         xs._NotifyState()
756         return nil
757 }
758
759 // _NotifyState Send event to notify changes
760 func (xs *XdsServer) _NotifyState() {
761
762         evSts := xaapiv1.ServerCfg{
763                 ID:         xs.ID,
764                 URL:        xs.BaseURL,
765                 APIURL:     xs.APIURL,
766                 PartialURL: xs.PartialURL,
767                 ConnRetry:  xs.ConnRetry,
768                 Connected:  xs.Connected,
769         }
770         if err := xs.events.Emit(xaapiv1.EVTServerConfig, evSts, ""); err != nil {
771                 xs.Log.Warningf("Cannot notify XdsServer state change: %v", err)
772         }
773 }