X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=lib%2Fsyncthing%2Fst.go;h=ea3db384665f1ca5ca35463126303c4f2bf3c253;hb=247bb7c2db5f0d48178398599348249bf886ebbc;hp=e51387626b17b8180d34e2e96e9a509e5f3b44d5;hpb=bfeab33538d50ee52750de4dd4c0e72b64f674f6;p=src%2Fxds%2Fxds-agent.git diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go index e513876..ea3db38 100644 --- a/lib/syncthing/st.go +++ b/lib/syncthing/st.go @@ -1,10 +1,30 @@ +/* + * Copyright (C) 2017-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 st import ( "encoding/json" + "io" "io/ioutil" "os" "path" + "path/filepath" + "regexp" "strings" "syscall" "time" @@ -13,99 +33,187 @@ import ( "os/exec" + "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xdsconfig" + common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git" "github.com/Sirupsen/logrus" - "github.com/iotbzh/xds-agent/lib/common" - "github.com/iotbzh/xds-agent/lib/xdsconfig" "github.com/syncthing/syncthing/lib/config" ) // SyncThing . type SyncThing struct { - BaseURL string - ApiKey string - Home string - STCmd *exec.Cmd - STICmd *exec.Cmd + BaseURL string + APIKey string + Home string + STCmd *exec.Cmd + STICmd *exec.Cmd + MyID string + Connected bool + Events *Events // Private fields binDir string + logsDir string exitSTChan chan ExitChan exitSTIChan chan ExitChan client *common.HTTPClient log *logrus.Logger + conf *xdsconfig.Config } -// Monitor process exit +// ExitChan Channel used for process exit type ExitChan struct { status int err error } +// ConfigInSync Check whether if Syncthing configuration is in sync +type configInSync struct { + ConfigInSync bool `json:"configInSync"` +} + +// FolderStatus Information about the current status of a folder. +type FolderStatus struct { + GlobalFiles int `json:"globalFiles"` + GlobalDirectories int `json:"globalDirectories"` + GlobalSymlinks int `json:"globalSymlinks"` + GlobalDeleted int `json:"globalDeleted"` + GlobalBytes int64 `json:"globalBytes"` + + LocalFiles int `json:"localFiles"` + LocalDirectories int `json:"localDirectories"` + LocalSymlinks int `json:"localSymlinks"` + LocalDeleted int `json:"localDeleted"` + LocalBytes int64 `json:"localBytes"` + + NeedFiles int `json:"needFiles"` + NeedDirectories int `json:"needDirectories"` + NeedSymlinks int `json:"needSymlinks"` + NeedDeletes int `json:"needDeletes"` + NeedBytes int64 `json:"needBytes"` + + InSyncFiles int `json:"inSyncFiles"` + InSyncBytes int64 `json:"inSyncBytes"` + + State string `json:"state"` + StateChanged time.Time `json:"stateChanged"` + + Sequence int64 `json:"sequence"` + + IgnorePatterns bool `json:"ignorePatterns"` +} + // NewSyncThing creates a new instance of Syncthing -//func NewSyncThing(url string, apiKey string, home string, log *logrus.Logger) *SyncThing { -func NewSyncThing(conf *xdsconfig.SyncThingConf, log *logrus.Logger) *SyncThing { - url := conf.GuiAddress - apiKey := conf.GuiAPIKey - home := conf.Home +func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing { + var url, apiKey, home, binDir string + + stCfg := conf.FileConf.SThgConf + if stCfg != nil { + url = stCfg.GuiAddress + apiKey = stCfg.GuiAPIKey + home = stCfg.Home + binDir = stCfg.BinDir + } + + if url == "" { + url = "http://localhost:8386" + } + if url[0:7] != "http://" { + url = "http://" + url + } + + if home == "" { + panic("home parameter must be set") + } s := SyncThing{ BaseURL: url, - ApiKey: apiKey, + APIKey: apiKey, Home: home, - binDir: conf.BinDir, + binDir: binDir, + logsDir: conf.FileConf.LogsDir, log: log, + conf: conf, } - if s.BaseURL == "" { - s.BaseURL = "http://localhost:8384" - } - if s.BaseURL[0:7] != "http://" { - s.BaseURL = "http://" + s.BaseURL - } + // Create Events monitoring + s.Events = s.NewEventListener() return &s } // Start Starts syncthing process func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) { + var err error + var exePath string // Kill existing process (useful for debug ;-) ) - if os.Getenv("DEBUG_MODE") != "" { - exec.Command("bash", "-c", "pkill -9 "+exeName).Output() + if _, dbg := os.LookupEnv("XDS_DEBUG_MODE"); dbg { + fmt.Printf("\n!!! DEBUG_MODE set: KILL existing %s process(es) !!!\n", exeName) + exec.Command("bash", "-c", "ps -ax |grep "+exeName+" |grep "+s.BaseURL+" |cut -d' ' -f 1|xargs -I{} kill -9 {}").Output() + } + + // When not set (or set to '.') set bin to path of xds-agent executable + bdir := s.binDir + if bdir == "" || bdir == "." { + exe, _ := os.Executable() + if exeAbsPath, err := filepath.Abs(exe); err == nil { + if exePath, err := filepath.EvalSymlinks(exeAbsPath); err == nil { + bdir = filepath.Dir(exePath) + } + } } - path, err := exec.LookPath(path.Join(s.binDir, exeName)) + exePath, err = exec.LookPath(path.Join(bdir, exeName)) if err != nil { - return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir) + // Let's try in /opt/AGL/bin + exePath, err = exec.LookPath(path.Join("opt", "AGL", "bin", exeName)) + if err != nil { + return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, bdir) + } } - cmd := exec.Command(path, args...) + cmd := exec.Command(exePath, args...) cmd.Env = os.Environ() for _, ev := range env { cmd.Env = append(cmd.Env, ev) } + // open log file + var outfile *os.File + logFilename := filepath.Join(s.logsDir, exeName+".log") + if s.logsDir != "" { + outfile, err := os.Create(logFilename) + if err != nil { + return nil, fmt.Errorf("Cannot create log file %s", logFilename) + } + + cmdOut, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("Pipe stdout error for : %v", err) + } + + go io.Copy(outfile, cmdOut) + } + err = cmd.Start() if err != nil { return nil, err } *eChan = make(chan ExitChan, 1) - go func(c *exec.Cmd) { + go func(c *exec.Cmd, oF *os.File) { status := 0 - cmdOut, err := c.StdoutPipe() - if err == nil { - s.log.Errorf("Pipe stdout error for : %s", err) - } else if cmdOut != nil { - stdOutput, _ := ioutil.ReadAll(cmdOut) - fmt.Printf("STDOUT: %s\n", stdOutput) - } sts, err := c.Process.Wait() if !sts.Success() { s := sts.Sys().(syscall.WaitStatus) status = s.ExitStatus() } + if oF != nil { + oF.Close() + } + s.log.Debugf("%s exited with status %d, err %v", exeName, status, err) + *eChan <- ExitChan{status, err} - }(cmd) + }(cmd, outfile) return cmd, nil } @@ -113,14 +221,19 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan // Start Starts syncthing process func (s *SyncThing) Start() (*exec.Cmd, error) { var err error + + s.log.Infof(" ST home=%s", s.Home) + s.log.Infof(" ST url=%s", s.BaseURL) + args := []string{ "--home=" + s.Home, "-no-browser", "--gui-address=" + s.BaseURL, } - if s.ApiKey != "" { - args = append(args, "-gui-apikey=\""+s.ApiKey+"\"") + if s.APIKey != "" { + args = append(args, "-gui-apikey=\""+s.APIKey+"\"") + s.log.Infof(" ST apikey=%s", s.APIKey) } if s.log.Level == logrus.DebugLevel { args = append(args, "-verbose") @@ -128,42 +241,95 @@ func (s *SyncThing) Start() (*exec.Cmd, error) { env := []string{ "STNODEFAULTFOLDER=1", + "STNOUPGRADE=1", } + /* FIXME - STILL NEEDED ?, if not SUP code + + // XXX - temporary hack because -gui-apikey seems to correctly handle by + // syncthing the early first time + stConfigFile := filepath.Join(s.Home, "config.xml") + if s.APIKey != "" && !common.Exists(stConfigFile) { + s.log.Infof("Stop and restart Syncthing (hack for apikey setting)") + s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan) + tmo := 20 + for ; tmo > 0; tmo-- { + s.log.Debugf("Waiting Syncthing config.xml creation (%v)\n", tmo) + time.Sleep(500 * time.Millisecond) + if common.Exists(stConfigFile) { + break + } + } + if tmo <= 0 { + return nil, fmt.Errorf("Cannot start Syncthing for config file creation") + } + s.Stop() + read, err := ioutil.ReadFile(stConfigFile) + if err != nil { + return nil, fmt.Errorf("Cannot read Syncthing config file for apikey setting") + } + re := regexp.MustCompile(`.*`) + newContents := re.ReplaceAllString(string(read), ""+s.APIKey+"") + err = ioutil.WriteFile(stConfigFile, []byte(newContents), 0) + if err != nil { + return nil, fmt.Errorf("Cannot write Syncthing config file to set apikey") + } + } + */ s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan) + // Use autogenerated apikey if not set by agent-config.json + if err == nil && s.APIKey == "" { + if fd, err := os.Open(filepath.Join(s.Home, "config.xml")); err == nil { + defer fd.Close() + if b, err := ioutil.ReadAll(fd); err == nil { + re := regexp.MustCompile("(.*)") + key := re.FindStringSubmatch(string(b)) + if len(key) >= 1 { + s.APIKey = key[1] + } + } + } + } + return s.STCmd, err } // StartInotify Starts syncthing-inotify process func (s *SyncThing) StartInotify() (*exec.Cmd, error) { var err error + exeName := "syncthing-inotify" + + s.log.Infof(" STI url=%s", s.BaseURL) args := []string{ - "--home=" + s.Home, "-target=" + s.BaseURL, } + if s.APIKey != "" { + args = append(args, "-api="+s.APIKey) + s.log.Infof("%s uses apikey=%s", exeName, s.APIKey) + } if s.log.Level == logrus.DebugLevel { args = append(args, "-verbosity=4") } env := []string{} - s.STICmd, err = s.startProc("syncthing-inotify", args, env, &s.exitSTIChan) + s.STICmd, err = s.startProc(exeName, args, env, &s.exitSTIChan) return s.STICmd, err } func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) { if err := proc.Signal(os.Interrupt); err != nil { - s.log.Errorf("Proc interrupt %s error: %s", pname, err.Error()) + s.log.Infof("Proc interrupt %s error: %s", pname, err.Error()) select { case <-exit: case <-time.After(time.Second): // A bigger bonk on the head. if err := proc.Signal(os.Kill); err != nil { - s.log.Errorf("Proc term %s error: %s", pname, err.Error()) + s.log.Infof("Proc term %s error: %s", pname, err.Error()) } <-exit } @@ -192,11 +358,17 @@ func (s *SyncThing) StopInotify() { // Connect Establish HTTP connection with Syncthing func (s *SyncThing) Connect() error { var err error + s.Connected = false s.client, err = common.HTTPNewClient(s.BaseURL, common.HTTPClientConfig{ URLPrefix: "/rest", HeaderClientKeyName: "X-Syncthing-ID", + LogOut: s.conf.LogVerboseOut, + LogPrefix: "SYNCTHING: ", + LogLevel: common.HTTPLogLevelWarning, }) + s.client.SetLogLevel(s.log.Level.String()) + if err != nil { msg := ": " + err.Error() if strings.Contains(err.Error(), "connection refused") { @@ -207,7 +379,18 @@ func (s *SyncThing) Connect() error { if s.client == nil { return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)") } - return nil + + s.MyID, err = s.IDGet() + if err != nil { + return fmt.Errorf("ERROR: cannot retrieve ID") + } + + s.Connected = true + + // Start events monitoring + err = s.Events.Start() + + return err } // IDGet returns the Syncthing ID of Syncthing instance running locally @@ -240,3 +423,16 @@ func (s *SyncThing) ConfigSet(cfg config.Configuration) error { } return s.client.HTTPPost("system/config", string(body)) } + +// IsConfigInSync Returns true if configuration is in sync +func (s *SyncThing) IsConfigInSync() (bool, error) { + var data []byte + var d configInSync + if err := s.client.HTTPGet("system/config/insync", &data); err != nil { + return false, err + } + if err := json.Unmarshal(data, &d); err != nil { + return false, err + } + return d.ConfigInSync, nil +}