Fixed subversion when tag doesn't include dash.
[src/xds/xds-server.git] / lib / xdsserver / xdsserver.go
1 package xdsserver
2
3 import (
4         "fmt"
5         "os"
6         "os/exec"
7         "os/signal"
8         "path/filepath"
9         "syscall"
10         "time"
11
12         "github.com/Sirupsen/logrus"
13         "github.com/codegangsta/cli"
14         "github.com/iotbzh/xds-server/lib/xsapiv1"
15
16         "github.com/iotbzh/xds-server/lib/syncthing"
17         "github.com/iotbzh/xds-server/lib/xdsconfig"
18 )
19
20 const cookieMaxAge = "3600"
21
22 // Context holds the XDS server context
23 type Context struct {
24         ProgName      string
25         Cli           *cli.Context
26         Config        *xdsconfig.Config
27         Log           *logrus.Logger
28         LogLevelSilly bool
29         SThg          *st.SyncThing
30         SThgCmd       *exec.Cmd
31         SThgInotCmd   *exec.Cmd
32         mfolders      *Folders
33         sdks          *SDKs
34         WWWServer     *WebServer
35         sessions      *Sessions
36         Exit          chan os.Signal
37 }
38
39 // NewXdsServer Create a new instance of XDS server
40 func NewXdsServer(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         sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY")
57
58         // Define default configuration
59         ctx := Context{
60                 ProgName:      cliCtx.App.Name,
61                 Cli:           cliCtx,
62                 Log:           log,
63                 LogLevelSilly: (sillyLog && sillyVal == "1"),
64                 Exit:          make(chan os.Signal, 1),
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 XDS Server
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.Sprintf("Cannot create log file %s", logFile)
87                                 return int(syscall.EPERM), fmt.Errorf(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-server-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.Sprintf("Cannot create log file %s", logFileHTTPReq)
98                         return int(syscall.EPERM), fmt.Errorf(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 -4, err
116                 }
117                 ctx._logPrint("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 -4, err
123                 }
124                 ctx._logPrint("Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
125
126                 // Establish connection with local Syncthing (retry if connection fail)
127                 ctx._logPrint("Establishing connection with Syncthing...\n")
128                 time.Sleep(2 * time.Second)
129                 maxRetry := 30
130                 retry := maxRetry
131                 err = nil
132                 for retry > 0 {
133                         if err = ctx.SThg.Connect(); err == nil {
134                                 break
135                         }
136                         ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
137                         time.Sleep(time.Second)
138                         retry--
139                 }
140                 if err != nil || retry == 0 {
141                         return -4, err
142                 }
143
144                 // FIXME: do we still need Builder notion ? if no cleanup
145                 if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil {
146                         return -4, err
147                 }
148                 ctx.Config.SupportedSharing[xsapiv1.TypeCloudSync] = true
149         }
150
151         // Init model folder
152         ctx.mfolders = FoldersNew(ctx)
153
154         // Load initial folders config from disk
155         if err := ctx.mfolders.LoadConfig(); err != nil {
156                 return -5, err
157         }
158
159         // Init cross SDKs
160         ctx.sdks, err = NewSDKs(ctx)
161         if err != nil {
162                 return -6, err
163         }
164
165         // Create Web Server
166         ctx.WWWServer = NewWebServer(ctx)
167
168         // Sessions manager
169         ctx.sessions = NewClientSessions(ctx, cookieMaxAge)
170
171         // Run Web Server until exit requested (blocking call)
172         if err = ctx.WWWServer.Serve(); err != nil {
173                 ctx.Log.Println(err)
174                 return -7, err
175         }
176
177         return -99, fmt.Errorf("Program exited ")
178 }
179
180 // Helper function to log message on both stdout and logger
181 func (ctx *Context) _logPrint(format string, args ...interface{}) {
182         fmt.Printf(format, args...)
183         if ctx.Log.Out != os.Stdout {
184                 ctx.Log.Infof(format, args...)
185         }
186 }
187
188 // Handle exit and properly stop/close all stuff
189 func handlerSigTerm(ctx *Context) {
190         <-ctx.Exit
191         if ctx.SThg != nil {
192                 ctx.Log.Infof("Stoping Syncthing... (PID %d)", ctx.SThgCmd.Process.Pid)
193                 ctx.SThg.Stop()
194                 ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)", ctx.SThgInotCmd.Process.Pid)
195                 ctx.SThg.StopInotify()
196         }
197         if ctx.WWWServer != nil {
198                 ctx.Log.Infof("Stoping Web server...")
199                 ctx.WWWServer.Stop()
200         }
201         os.Exit(0)
202 }