diff --git a/cfg.example.json b/cfg.example.json index 2119f3e4..491fdce7 100644 --- a/cfg.example.json +++ b/cfg.example.json @@ -2,12 +2,22 @@ "debug": true, "hostname": "", "ip": "", + "pidfile":"./var/app.pid", "plugin": { "enabled": false, "dir": "./plugin", "git": "https://github.com/open-falcon/plugin.git", "logs": "./logs" }, + "autoupdate":{ + "enabled": false, + "dir":"./agent", + "url":"http://127.0.0.1", + "tar":"falcon-agent_%s.tar.gz", + "interval":60, + "randInt":300, + "timeout":1000 + }, "heartbeat": { "enabled": true, "addr": "127.0.0.1:6030", diff --git a/cron/agent.go b/cron/agent.go new file mode 100644 index 00000000..360ee057 --- /dev/null +++ b/cron/agent.go @@ -0,0 +1,50 @@ +package cron + +import ( + "github.com/open-falcon/agent/g" + "github.com/open-falcon/common/model" + "log" + "math/rand" + "time" +) + +func SyncMineAgentVersion() { + if g.Config().AutoUpdate.Enabled && g.Config().Heartbeat.Enabled && g.Config().Heartbeat.Addr != "" { + go syncMineAgentVersion() + } +} + +func syncMineAgentVersion() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + duration := time.Duration(g.Config().AutoUpdate.Interval) * time.Second + randInt := g.Config().AutoUpdate.RandInt + + for { + time.Sleep(duration) + hostname, err := g.Hostname() + if err != nil { + return + } + + rtime := r.Intn(randInt) + time.Sleep(time.Duration(rtime) * time.Second) + + req := model.AgentHeartbeatRequest{ + Hostname: hostname, + } + + var version string + err = g.HbsClient.Call("Agent.MineAgentVersion", req, &version) + if err != nil { + log.Println("ERROR:", err) + continue + } + if version == "" { + continue + } + if g.VERSION != version { + log.Printf("Agent from %s to %s.", g.VERSION, version) + g.AutoUpdateChk(version) + } + } +} diff --git a/g/cfg.go b/g/cfg.go index 6baaa99a..66a39f60 100644 --- a/g/cfg.go +++ b/g/cfg.go @@ -16,6 +16,16 @@ type PluginConfig struct { LogDir string `json:"logs"` } +type AutoUpdateConfig struct { + Enabled bool `json:"enabled"` + Dir string `json:"dir"` + Url string `json:"url"` + Tar string `json:"tar"` + Interval int `json:"interval"` + RandInt int `json:"randInt"` + Timeout int `json:"timeout"` +} + type HeartbeatConfig struct { Enabled bool `json:"enabled"` Addr string `json:"addr"` @@ -41,15 +51,17 @@ type CollectorConfig struct { } type GlobalConfig struct { - Debug bool `json:"debug"` - Hostname string `json:"hostname"` - IP string `json:"ip"` - Plugin *PluginConfig `json:"plugin"` - Heartbeat *HeartbeatConfig `json:"heartbeat"` - Transfer *TransferConfig `json:"transfer"` - Http *HttpConfig `json:"http"` - Collector *CollectorConfig `json:"collector"` - IgnoreMetrics map[string]bool `json:"ignore"` + Debug bool `json:"debug"` + Hostname string `json:"hostname"` + IP string `json:"ip"` + PidFile string `json:"pidfile"` + Plugin *PluginConfig `json:"plugin"` + AutoUpdate *AutoUpdateConfig `json:"autoupdate"` + Heartbeat *HeartbeatConfig `json:"heartbeat"` + Transfer *TransferConfig `json:"transfer"` + Http *HttpConfig `json:"http"` + Collector *CollectorConfig `json:"collector"` + IgnoreMetrics map[string]bool `json:"ignore"` } var ( diff --git a/g/update.go b/g/update.go new file mode 100644 index 00000000..1f1ee0cf --- /dev/null +++ b/g/update.go @@ -0,0 +1,166 @@ +package g + +import ( + "errors" + "fmt" + "github.com/toolkits/file" + "github.com/toolkits/sys" + "io" + "log" + "net/http" + "os" + "path/filepath" + "syscall" + "time" +) + +const ( + ExecFile = "falcon-agent" + TmpExecFile = "falcon-agent-tmp" +) + +func RestartDaemon() { + execSpec := &syscall.ProcAttr{ + Env: os.Environ(), + Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, + } + fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec) + if err != nil { + log.Printf("Fail to fork new process: %s", err) + return + } + log.Printf("Fork new process id: %d", fork) + log.Printf("Old server(%d) gracefully shutdown.", os.Getpid()) + pidfile := Config().PidFile + f, err := os.Create(pidfile) + if err != nil { + log.Printf("write %s err: %s", pidfile, err) + f.Close() + } else { + pidstr := fmt.Sprintf("%d\n", fork) + f.WriteString(pidstr) + f.Close() + } + // stop the old server + os.Exit(0) +} + +func AutoUpdateChk(v string) { + if !Config().AutoUpdate.Enabled { + return + } + var err error + file.Remove(TmpExecFile) + tar_prefix := Config().AutoUpdate.Tar + agent_new := fmt.Sprintf(tar_prefix, v) + err = UpdateAgent(agent_new) + if err != nil { + log.Println("Get Agent Fail with : ", err) + return + } + + RestartDaemon() +} + +func UpdateAgent(filename string) error { + fpath := file.SelfDir() + server_url := Config().AutoUpdate.Url + url := fmt.Sprintf("%s/%s", server_url, filename) + var err error + debug := Config().Debug + timeout := time.Duration(Config().AutoUpdate.Timeout) * time.Millisecond + if debug { + log.Println("Downloading", url, "to", filename) + } + err = file.EnsureDir(Config().AutoUpdate.Dir) + if err != nil { + return err + } + rel_filename := filepath.Join(Config().AutoUpdate.Dir, filename) + + client := http.Client{ + Timeout: timeout, + } + response, err := client.Get(url) + if err != nil { + return err + } + + defer response.Body.Close() + + if response.StatusCode != 200 && response.StatusCode != 301 && response.StatusCode != 302 { + errtxt := fmt.Sprintf("response code is %d", response.StatusCode) + return errors.New(errtxt) + } + + output, err := os.Create(rel_filename) + if err != nil { + return err + } + defer output.Close() + + _, err = io.Copy(output, response.Body) + if err != nil { + return err + } + + err = UnCompressAgentTest(rel_filename) + if err != nil { + return err + } + + err = UnCompressAgent(rel_filename, fpath) + if err != nil { + return err + } + + return nil +} + +func UnCompressAgent(filename, destdir string) error { + var err error + err = file.Rename(ExecFile, TmpExecFile) + if err != nil { + return err + } + err = UnTarGz(filename, destdir) + if err != nil { + return err + } + if !file.IsExist(ExecFile) { + file.Rename(TmpExecFile, ExecFile) + return errors.New("File not found,rollback now.") + } + file.Remove(filename) + return nil +} + +func UnCompressAgentTest(filename string) error { + var err error + tmpdir := filepath.Join(Config().AutoUpdate.Dir, "tmp") + if file.IsExist(ExecFile) { + err = os.RemoveAll(tmpdir) + if err != nil { + return err + } + } + file.EnsureDir(tmpdir) + err = UnTarGz(filename, tmpdir) + if err != nil { + return err + } + + if !file.IsExist(filepath.Join(tmpdir, ExecFile)) { + return errors.New("File not found.") + } + os.RemoveAll(tmpdir) + return nil +} + +func UnTarGz(srcFilePath string, destDirPath string) error { + _, err := sys.CmdOutBytes("tar", "zxf", srcFilePath, "-C", destDirPath) + if err != nil { + return err + } + return nil +} diff --git a/main.go b/main.go index 7ad0d358..edd153a0 100644 --- a/main.go +++ b/main.go @@ -40,6 +40,7 @@ func main() { cron.ReportAgentStatus() cron.SyncMinePlugins() + cron.SyncMineAgentVersion() cron.SyncBuiltinMetrics() cron.SyncTrustableIps() cron.Collect()