From 2e17a524738982174794370a2bb609872608a5ac Mon Sep 17 00:00:00 2001 From: =?utf8?q?Cl=C3=A9ment=20B=C3=A9nier?= Date: Mon, 1 Oct 2018 11:51:47 +0200 Subject: [PATCH] update xds-server package in docker MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit scripts/package-update: bash scripts to update xds-server package in docker monitor updates: default time is 24h, a update is tried at startup api/v1: 2 new requests: - get(updates): return various information about xds-server packages - post(updates): update xds-server packages Change-Id: I34613be916bb29c2cce13ca1cce1e384365872f7 Signed-off-by: Clément Bénier --- Makefile | 2 +- conf.d/etc/xds/server/server-config.json | 1 + lib/xdsconfig/config.go | 30 +++--- lib/xdsconfig/fileconfig.go | 26 +++-- lib/xdsserver/apiv1-exec.go | 9 ++ lib/xdsserver/apiv1-sdks.go | 12 +++ lib/xdsserver/apiv1-updates.go | 40 ++++++++ lib/xdsserver/apiv1.go | 3 + lib/xdsserver/webserver.go | 13 +++ lib/xdsserver/xds-server-update.go | 138 ++++++++++++++++++++++++++ lib/xdsserver/xdsserver.go | 6 ++ lib/xsapiv1/xds-server-update.go | 25 +++++ scripts/package-update/getXdsServerPackage | 31 ++++++ scripts/package-update/restartXdsServer | 15 +++ scripts/package-update/updateXdsServerPackage | 31 ++++++ 15 files changed, 360 insertions(+), 22 deletions(-) create mode 100644 lib/xdsserver/apiv1-updates.go create mode 100644 lib/xdsserver/xds-server-update.go create mode 100644 lib/xsapiv1/xds-server-update.go create mode 100755 scripts/package-update/getXdsServerPackage create mode 100755 scripts/package-update/restartXdsServer create mode 100755 scripts/package-update/updateXdsServerPackage diff --git a/Makefile b/Makefile index 64ba5a3..8dec007 100644 --- a/Makefile +++ b/Makefile @@ -158,7 +158,7 @@ webapp/install: .PHONY: scripts scripts: - @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/xds-utils scripts/sdks $(LOCAL_BINDIR) + @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/xds-utils scripts/sdks scripts/package-update $(LOCAL_BINDIR) .PHONY: conffile conffile: diff --git a/conf.d/etc/xds/server/server-config.json b/conf.d/etc/xds/server/server-config.json index 47b75ef..5d23a6f 100644 --- a/conf.d/etc/xds/server/server-config.json +++ b/conf.d/etc/xds/server/server-config.json @@ -3,6 +3,7 @@ "httpPort": "8000", "shareRootDir": "${HOME}/.xds/server/projects", "sdkScriptsDir": "${EXEPATH}/sdks", + "xdsSrvUpdateScriptsDir": "${EXEPATH}/package-update", "syncthing": { "binDir": "", "home": "${HOME}/.xds/server/syncthing-config", diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go index 4ba97ef..ab0539c 100644 --- a/lib/xdsconfig/config.go +++ b/lib/xdsconfig/config.go @@ -53,12 +53,14 @@ type Options struct { // Config default values const ( - DefaultAPIVersion = "1" - DefaultPort = "8000" - DefaultShareDir = "projects" - DefaultSTHomeDir = "syncthing-config" - DefaultSdkScriptsDir = "${EXEPATH}/sdks" - DefaultSdkDbUpdate = "startup" + DefaultAPIVersion = "1" + DefaultPort = "8000" + DefaultShareDir = "projects" + DefaultSTHomeDir = "syncthing-config" + DefaultSdkScriptsDir = "${EXEPATH}/sdks" + DefaultXdsSrvUpdateScriptsDir = "${EXEPATH}/package-update" + DefaultSdkDbUpdate = "startup" + DefaultXdsSrvUpdateTime = "24h" ) // Init loads the configuration on start-up @@ -98,13 +100,15 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { NoFolderConfig: cliCtx.GlobalBool("no-folderconfig"), }, FileConf: FileConfig{ - WebAppDir: "webapp/dist", - ShareRootDir: dfltShareDir, - SdkScriptsDir: DefaultSdkScriptsDir, - SdkDbUpdate: DefaultSdkDbUpdate, - HTTPPort: DefaultPort, - SThgConf: &SyncThingConf{Home: dfltSTHomeDir}, - LogsDir: "", + WebAppDir: "webapp/dist", + ShareRootDir: dfltShareDir, + SdkScriptsDir: DefaultSdkScriptsDir, + XdsSrvUpdateScriptsDir: DefaultXdsSrvUpdateScriptsDir, + SdkDbUpdate: DefaultSdkDbUpdate, + HTTPPort: DefaultPort, + SThgConf: &SyncThingConf{Home: dfltSTHomeDir}, + LogsDir: "", + XdsSrvUpdateTime: DefaultXdsSrvUpdateTime, }, Log: log, } diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go index b786a24..0b248cd 100644 --- a/lib/xdsconfig/fileconfig.go +++ b/lib/xdsconfig/fileconfig.go @@ -50,13 +50,15 @@ type SyncThingConf struct { // FileConfig is the JSON structure of xds-server config file (server-config.json) type FileConfig struct { - WebAppDir string `json:"webAppDir"` - ShareRootDir string `json:"shareRootDir"` - SdkScriptsDir string `json:"sdkScriptsDir"` - SdkDbUpdate string `json:"sdkDbUpdate"` - HTTPPort string `json:"httpPort"` - SThgConf *SyncThingConf `json:"syncthing"` - LogsDir string `json:"logsDir"` + WebAppDir string `json:"webAppDir"` + ShareRootDir string `json:"shareRootDir"` + SdkScriptsDir string `json:"sdkScriptsDir"` + XdsSrvUpdateScriptsDir string `json:"xdsSrvUpdateScriptsDir"` + SdkDbUpdate string `json:"sdkDbUpdate"` + HTTPPort string `json:"httpPort"` + SThgConf *SyncThingConf `json:"syncthing"` + LogsDir string `json:"logsDir"` + XdsSrvUpdateTime string `json:"xdsSrvUpdateTime"` } // readGlobalConfig reads configuration from a config file. @@ -118,7 +120,9 @@ func readGlobalConfig(c *Config, confFile string) error { &fCfg.WebAppDir, &fCfg.ShareRootDir, &fCfg.SdkScriptsDir, - &fCfg.LogsDir} + &fCfg.XdsSrvUpdateScriptsDir, + &fCfg.LogsDir, + &fCfg.XdsSrvUpdateTime} if fCfg.SThgConf != nil { vars = append(vars, &fCfg.SThgConf.Home, &fCfg.SThgConf.BinDir) } @@ -139,6 +143,9 @@ func readGlobalConfig(c *Config, confFile string) error { if fCfg.SdkScriptsDir == "" { fCfg.SdkScriptsDir = c.FileConf.SdkScriptsDir } + if fCfg.XdsSrvUpdateScriptsDir == "" { + fCfg.XdsSrvUpdateScriptsDir = c.FileConf.XdsSrvUpdateScriptsDir + } if fCfg.SdkDbUpdate == "" { fCfg.SdkDbUpdate = c.FileConf.SdkDbUpdate } @@ -148,6 +155,9 @@ func readGlobalConfig(c *Config, confFile string) error { if fCfg.LogsDir == "" { fCfg.LogsDir = c.FileConf.LogsDir } + if fCfg.XdsSrvUpdateTime == "" { + fCfg.XdsSrvUpdateTime = c.FileConf.XdsSrvUpdateTime + } // Resolve webapp dir (support relative or full path) fCfg.WebAppDir = strings.Trim(fCfg.WebAppDir, " ") diff --git a/lib/xdsserver/apiv1-exec.go b/lib/xdsserver/apiv1-exec.go index 57ea1f1..c045e9e 100644 --- a/lib/xdsserver/apiv1-exec.go +++ b/lib/xdsserver/apiv1-exec.go @@ -314,6 +314,9 @@ func (s *APIService) execCmd(c *gin.Context) { if errSoEmit != nil { s.Log.Errorf("WS Emit : %v", errSoEmit) } + s.lock.Lock() + s.lock.LockCpt-- + s.lock.Unlock() } // User data (used within callbacks) @@ -330,8 +333,14 @@ func (s *APIService) execCmd(c *gin.Context) { // Start command execution s.Log.Infof("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args) + s.lock.Lock() + s.lock.LockCpt++ + s.lock.Unlock() err = execWS.Start() if err != nil { + s.lock.Lock() + s.lock.LockCpt-- + s.lock.Unlock() common.APIError(c, err.Error()) return } diff --git a/lib/xdsserver/apiv1-sdks.go b/lib/xdsserver/apiv1-sdks.go index 7bb9767..2f875ea 100644 --- a/lib/xdsserver/apiv1-sdks.go +++ b/lib/xdsserver/apiv1-sdks.go @@ -74,8 +74,14 @@ func (s *APIService) installSdk(c *gin.Context) { return } + s.lock.Lock() + s.lock.LockCpt++ + s.lock.Unlock() sdk, err := s.sdks.Install(id, args.Filename, args.Force, args.Timeout, args.InstallArgs, sess) if err != nil { + s.lock.Lock() + s.lock.LockCpt-- + s.lock.Unlock() common.APIError(c, err.Error()) return } @@ -104,6 +110,9 @@ func (s *APIService) abortInstallSdk(c *gin.Context) { } c.JSON(http.StatusOK, sdk) + s.lock.Lock() + s.lock.LockCpt-- + s.lock.Unlock() } // removeSdk Uninstall a Sdk @@ -129,4 +138,7 @@ func (s *APIService) removeSdk(c *gin.Context) { return } c.JSON(http.StatusOK, delEntry) + s.lock.Lock() + s.lock.LockCpt-- + s.lock.Unlock() } diff --git a/lib/xdsserver/apiv1-updates.go b/lib/xdsserver/apiv1-updates.go new file mode 100644 index 0000000..930ec64 --- /dev/null +++ b/lib/xdsserver/apiv1-updates.go @@ -0,0 +1,40 @@ +/* + * 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 xdsserver + +import ( + "net/http" + + common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib" + "github.com/gin-gonic/gin" +) + +// getXdsSrvUpdate : return various information about pkg update +func (s *APIService) getXdsSrvUpdate(c *gin.Context) { + response := GetXdsSrvUpdate(s.Context) + c.JSON(http.StatusOK, response) +} + +// xdsSrvUpdate: update xds server package +func (s *APIService) xdsSrvUpdate(c *gin.Context) { + if err := UpdateXdsServer(s.Context); err != 0 { + common.APIError(c, "cannot update package") + return + } + c.JSON(http.StatusOK, "OK") +} diff --git a/lib/xdsserver/apiv1.go b/lib/xdsserver/apiv1.go index e0bfa7f..e394d9e 100644 --- a/lib/xdsserver/apiv1.go +++ b/lib/xdsserver/apiv1.go @@ -78,5 +78,8 @@ func NewAPIV1(ctx *Context) *APIService { s.apiRouter.POST("/targets/:id/terminals/:tid/signal", s.signalTgtTerm) s.apiRouter.POST("/targets/:id/terminals/:tid/signal/:sig", s.signalTgtTerm) + s.apiRouter.GET("/updates", s.getXdsSrvUpdate) + s.apiRouter.POST("/updates", s.xdsSrvUpdate) + return s } diff --git a/lib/xdsserver/webserver.go b/lib/xdsserver/webserver.go index 24456b9..2654caa 100644 --- a/lib/xdsserver/webserver.go +++ b/lib/xdsserver/webserver.go @@ -84,6 +84,7 @@ func (s *WebServer) Serve() error { s.router.Use(gin.Recovery()) s.router.Use(s.middlewareXDSDetails()) s.router.Use(s.middlewareCORS()) + s.router.Use(s.lockRequest()) // Create REST API s.api = NewAPIV1(s.Context) @@ -172,6 +173,18 @@ func (s *WebServer) middlewareCORS() gin.HandlerFunc { } } +func (s *WebServer) lockRequest() gin.HandlerFunc { + return func(c *gin.Context) { + s.lock.Lock() + s.lock.LockCpt++ + s.lock.Unlock() + c.Next() + s.lock.Lock() + s.lock.LockCpt-- + s.lock.Unlock() + } +} + // socketHandler is the handler for the "main" websocket connection func (s *WebServer) socketHandler(c *gin.Context) { diff --git a/lib/xdsserver/xds-server-update.go b/lib/xdsserver/xds-server-update.go new file mode 100644 index 0000000..6fcf9d9 --- /dev/null +++ b/lib/xdsserver/xds-server-update.go @@ -0,0 +1,138 @@ +/* + * 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 xdsserver + +import ( + "os/exec" + "path" + "path/filepath" + "strings" + sc "sync" + "time" + + common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib" + "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1" +) + +const ( + scriptXdsSrvUpdate = "updateXdsServerPackage" + scriptGetXdsSrvUpdate = "getXdsServerPackage" + scriptXdsSrvRestart = "restartXdsServer" +) + +//Lock allows to lock xds-server avoiding restart +type Lock struct { + sc.Mutex + LockCpt int +} + +// MonitorUpdates try to update xds-server package at first +// then monitor updates +func MonitorUpdates(ctx *Context) { + UpdateXdsServer(ctx) //try to update at startup + + updateTime, err := time.ParseDuration(ctx.Config.FileConf.XdsSrvUpdateTime) + if err != nil { + ctx.Log.Errorf("Wrong format type of XdsSrvUpdateTime\n"+ + "err=%v \n"+ + "Valid time units are ns, us, ms, s, m, h\n"+ + "Here an example: 1h10m10s", err) + } else { + ctx.Log.Infof("Update time for package xds-server is %v", updateTime) + go func(ctx *Context) { + for { + currentUpdateTime := updateTime + for currentUpdateTime > 0*time.Minute { + time.Sleep(currentUpdateTime) + currentUpdateTime = UpdateXdsServer(ctx) + } + } + }(ctx) + } +} + +func getScriptsDir(ctx *Context) string { + scriptsDir := ctx.Config.FileConf.XdsSrvUpdateScriptsDir + if !common.Exists(scriptsDir) { + // allow to use scripts/sdk in debug mode + scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.XdsSrvUpdateScriptsDir), "scripts", "package-update") + if !common.Exists(scriptsDir) { + ctx.Log.Errorf("scripts directory doesn't exist (%v)", scriptsDir) + } + } + return scriptsDir +} + +// UpdateXdsServer launches update package xds-server script +func UpdateXdsServer(ctx *Context) time.Duration { + timeToRestartIfBusy := 0 * time.Minute + scriptsDir := getScriptsDir(ctx) + + ctx.Log.Infof("Trying to update xds-server package, "+ + "package-update scripts dir: %s", scriptsDir) + + cmd := exec.Command(path.Join(scriptsDir, scriptXdsSrvUpdate)) + _, err := cmd.CombinedOutput() + if err != nil { + ctx.Log.Errorf("Cannot update xds-server package err=%v", err) + return 0 + } + + cmd = exec.Command(path.Join(scriptsDir, scriptXdsSrvRestart)) + ctx.lock.Lock() + if ctx.lock.LockCpt == 0 { //no action in progress + _, err = cmd.CombinedOutput() + if err != nil { + ctx.Log.Errorf("Cannot restart xds-server service err=%v", err) + return 0 + } + } else { + timeToRestartIfBusy = 1 * time.Minute + ctx.Log.Infof("Cannot restart xds-server service because "+ + "xds-server has an action in progress, trying to restart in a %v", timeToRestartIfBusy) + } + ctx.lock.Unlock() + + return timeToRestartIfBusy +} + +// GetXdsSrvUpdate gets information about package +func GetXdsSrvUpdate(ctx *Context) xsapiv1.XdsSrvUpdate { + var xdsSrvUpdate xsapiv1.XdsSrvUpdate + scriptsDir := getScriptsDir(ctx) + + //exec getXdsSrvUpdate script + cmd := exec.Command(path.Join(scriptsDir, scriptGetXdsSrvUpdate)) + stdout, err := cmd.CombinedOutput() + if err != nil { + ctx.Log.Errorf("Cannot get xds-server package information err=%v", err) + return xdsSrvUpdate + } + + //stdout is formatting with 'version: xxxxx' + outputs := strings.Split(string(stdout[:]), "\n") + installedVersion := strings.Split(outputs[0], ": ")[1] + candidateVersion := strings.Split(outputs[1], ": ")[1] + ctx.Log.Infof("XdsSrvUpdate: candidateVersion:%v installedVersion:%v", candidateVersion, installedVersion) + xdsSrvUpdate = xsapiv1.XdsSrvUpdate{ + CurrentVersion: installedVersion, + NewerVersion: candidateVersion, + UpdateTime: ctx.Config.FileConf.XdsSrvUpdateTime, + } + return xdsSrvUpdate +} diff --git a/lib/xdsserver/xdsserver.go b/lib/xdsserver/xdsserver.go index 1079eba..ba2a560 100644 --- a/lib/xdsserver/xdsserver.go +++ b/lib/xdsserver/xdsserver.go @@ -52,6 +52,7 @@ type Context struct { WWWServer *WebServer sessions *Sessions events *Events + lock Lock Exit chan os.Signal } @@ -88,6 +89,7 @@ func NewXdsServer(cliCtx *cli.Context) *Context { Log: log, LogLevelSilly: logSilly, LogSillyf: sillyFunc, + lock: Lock{LockCpt: 0}, Exit: make(chan os.Signal, 1), } @@ -206,6 +208,10 @@ func (ctx *Context) Run() (int, error) { // Sessions manager ctx.sessions = ClientSessionsConstructor(ctx, cookieMaxAge) + // Check if a new package version is available + // and monitor updates + MonitorUpdates(ctx) + // Run Web Server until exit requested (blocking call) if err = ctx.WWWServer.Serve(); err != nil { ctx.Log.Println(err) diff --git a/lib/xsapiv1/xds-server-update.go b/lib/xsapiv1/xds-server-update.go new file mode 100644 index 0000000..0c3f793 --- /dev/null +++ b/lib/xsapiv1/xds-server-update.go @@ -0,0 +1,25 @@ +/* + * 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 xsapiv1 + +// XdsSrvUpdate XDS server Version +type XdsSrvUpdate struct { + CurrentVersion string `json:"currentVersion"` + NewerVersion string `json:"newerVersion"` + UpdateTime string `json:"updateTime"` +} diff --git a/scripts/package-update/getXdsServerPackage b/scripts/package-update/getXdsServerPackage new file mode 100755 index 0000000..e54d09e --- /dev/null +++ b/scripts/package-update/getXdsServerPackage @@ -0,0 +1,31 @@ +#!/bin/bash + +XDSSERVER="xds-server" +PACKAGE_NAME="agl-${XDSSERVER}" + +#test if sudoers with NOPASSWD +sudo -l | grep NOPASSWD &> /dev/null +if [ ! $? -eq 0 ]; then + exit 1 +fi + +function getUpdateDebian() { + local policy="/tmp/policy" + sudo apt-get update &> /dev/null + apt-cache policy ${PACKAGE_NAME} > ${policy} + cat $policy | grep "Installed" + cat $policy | grep "Candidate" + rm -f $policy +} + +if [ -f /etc/os-release ]; then + source /etc/os-release +fi + +case $ID in + "debian") + getUpdateDebian;; + *) + exit 1;; +esac + diff --git a/scripts/package-update/restartXdsServer b/scripts/package-update/restartXdsServer new file mode 100755 index 0000000..40dfee9 --- /dev/null +++ b/scripts/package-update/restartXdsServer @@ -0,0 +1,15 @@ +#!/bin/bash + +XDSSERVER="xds-server" + +function systemdRestartXdsServer() { + systemctl --user daemon-reload + systemctl --user restart ${XDSSERVER} + if [ ! $? -eq 0 ]; then + echo "Cannot restart $XDSSERVER" + exit 1 + fi +} + +systemdRestartXdsServer +exit 0 \ No newline at end of file diff --git a/scripts/package-update/updateXdsServerPackage b/scripts/package-update/updateXdsServerPackage new file mode 100755 index 0000000..4f638b8 --- /dev/null +++ b/scripts/package-update/updateXdsServerPackage @@ -0,0 +1,31 @@ +#!/bin/bash + +XDSSERVER="xds-server" +PACKAGE_NAME="agl-${XDSSERVER}" + +#test if sudoers with NOPASSWD +sudo -l | grep NOPASSWD +if [ ! $? -eq 0 ]; then + exit 1 +fi + + +function checkUpdateDebian() { + sudo apt-get update + apt list --upgradable | grep ${PACKAGE_NAME} + if [ $? -eq 0 ]; then + sudo apt-get install ${PACKAGE_NAME} + fi +} + +if [ -f /etc/os-release ]; then + source /etc/os-release +fi + +case $ID in + "debian") + res=$(checkUpdateDebian);; + *) + exit 1;; +esac + -- 2.16.6