Added webapp Dashboard + logic to interact with server.
[src/xds/xds-agent.git] / lib / agent / agent.go
1 package agent
2
3 import (
4         "fmt"
5         "log"
6         "os"
7         "os/exec"
8         "os/signal"
9         "path/filepath"
10         "syscall"
11         "time"
12
13         "github.com/Sirupsen/logrus"
14         "github.com/codegangsta/cli"
15         "github.com/iotbzh/xds-agent/lib/syncthing"
16         "github.com/iotbzh/xds-agent/lib/xdsconfig"
17 )
18
19 const cookieMaxAge = "3600"
20
21 // Context holds the Agent context structure
22 type Context struct {
23         ProgName    string
24         Config      *xdsconfig.Config
25         Log         *logrus.Logger
26         SThg        *st.SyncThing
27         SThgCmd     *exec.Cmd
28         SThgInotCmd *exec.Cmd
29
30         webServer     *WebServer
31         xdsServers map[string]*XdsServer
32         sessions      *Sessions
33         events        *Events
34         projects      *Projects
35
36         Exit chan os.Signal
37 }
38
39 // NewAgent Create a new instance of Agent
40 func NewAgent(cliCtx *cli.Context) *Context {
41         var err error
42
43         // Set logger level and formatter
44         log := cliCtx.App.Metadata["logger"].(*logrus.Logger)
45
46         logLevel := cliCtx.GlobalString("log")
47         if logLevel == "" {
48                 logLevel = "error" // FIXME get from Config DefaultLogLevel
49         }
50         if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
51                 fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
52                 os.Exit(1)
53         }
54         log.Formatter = &logrus.TextFormatter{}
55
56         // Define default configuration
57         ctx := Context{
58                 ProgName: cliCtx.App.Name,
59                 Log:      log,
60                 Exit:     make(chan os.Signal, 1),
61
62                 webServer:     nil,
63                 xdsServers: make(map[string]*XdsServer),
64                 events:        nil,
65         }
66
67         // register handler on SIGTERM / exit
68         signal.Notify(ctx.Exit, os.Interrupt, syscall.SIGTERM)
69         go handlerSigTerm(&ctx)
70
71         return &ctx
72 }
73
74 // Run Main function called to run agent
75 func (ctx *Context) Run() (int, error) {
76         var err error
77
78         // Logs redirected into a file when logfile option or logsDir config is set
79         ctx.Config.LogVerboseOut = os.Stderr
80         if ctx.Config.FileConf.LogsDir != "" {
81                 if ctx.Config.Options.LogFile != "stdout" {
82                         logFile := ctx.Config.Options.LogFile
83
84                         fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
85                         if err != nil {
86                                 msgErr := fmt.Errorf("Cannot create log file %s", logFile)
87                                 return int(syscall.EPERM), msgErr
88                         }
89                         ctx.Log.Out = fdL
90
91                         ctx._logPrint("Logging file: %s\n", logFile)
92                 }
93
94                 logFileHTTPReq := filepath.Join(ctx.Config.FileConf.LogsDir, "xds-agent-verbose.log")
95                 fdLH, err := os.OpenFile(logFileHTTPReq, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
96                 if err != nil {
97                         msgErr := fmt.Errorf("Cannot create log file %s", logFileHTTPReq)
98                         return int(syscall.EPERM), msgErr
99                 }
100                 ctx.Config.LogVerboseOut = fdLH
101
102                 ctx._logPrint("Logging file for HTTP requests: %s\n", logFileHTTPReq)
103         }
104
105         // Create syncthing instance when section "syncthing" is present in config.json
106         if ctx.Config.FileConf.SThgConf != nil {
107                 ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
108         }
109
110         // Start local instance of Syncthing and Syncthing-notify
111         if ctx.SThg != nil {
112                 ctx.Log.Infof("Starting Syncthing...")
113                 ctx.SThgCmd, err = ctx.SThg.Start()
114                 if err != nil {
115                         return 2, err
116                 }
117                 fmt.Printf("Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid)
118
119                 ctx.Log.Infof("Starting Syncthing-inotify...")
120                 ctx.SThgInotCmd, err = ctx.SThg.StartInotify()
121                 if err != nil {
122                         return 2, err
123                 }
124                 fmt.Printf("Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
125
126                 // Establish connection with local Syncthing (retry if connection fail)
127                 time.Sleep(3 * time.Second)
128                 maxRetry := 30
129                 retry := maxRetry
130                 for retry > 0 {
131                         if err := ctx.SThg.Connect(); err == nil {
132                                 break
133                         }
134                         ctx.Log.Infof("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
135                         time.Sleep(time.Second)
136                         retry--
137                 }
138                 if err != nil || retry == 0 {
139                         return 2, err
140                 }
141
142                 // Retrieve Syncthing config
143                 id, err := ctx.SThg.IDGet()
144                 if err != nil {
145                         return 2, err
146                 }
147                 ctx.Log.Infof("Local Syncthing ID: %s", id)
148
149         } else {
150                 ctx.Log.Infof("Cloud Sync / Syncthing not supported")
151         }
152
153         // Create Web Server
154         ctx.webServer = NewWebServer(ctx)
155
156         // Sessions manager
157         ctx.sessions = NewClientSessions(ctx, cookieMaxAge)
158
159         // Create events management
160         ctx.events = NewEvents(ctx)
161
162         // Create projects management
163         ctx.projects = NewProjects(ctx, ctx.SThg)
164
165         // Run Web Server until exit requested (blocking call)
166         if err = ctx.webServer.Serve(); err != nil {
167                 log.Println(err)
168                 return 3, err
169         }
170
171         return 4, fmt.Errorf("Program exited")
172 }
173
174 // Helper function to log message on both stdout and logger
175 func (ctx *Context) _logPrint(format string, args ...interface{}) {
176         fmt.Printf(format, args...)
177         if ctx.Log.Out != os.Stdout {
178                 ctx.Log.Infof(format, args...)
179         }
180 }
181
182 // Handle exit and properly stop/close all stuff
183 func handlerSigTerm(ctx *Context) {
184         <-ctx.Exit
185         if ctx.SThg != nil {
186                 ctx.Log.Infof("Stoping Syncthing... (PID %d)",
187                         ctx.SThgCmd.Process.Pid)
188                 ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)",
189                         ctx.SThgInotCmd.Process.Pid)
190                 ctx.SThg.Stop()
191                 ctx.SThg.StopInotify()
192         }
193         if ctx.webServer != nil {
194                 ctx.Log.Infof("Stoping Web server...")
195                 ctx.webServer.Stop()
196         }
197         os.Exit(1)
198 }