update xds-server package in docker 69/16869/2 sandbox/benierc/xds-server-update-pkg
authorClément Bénier <clement.benier@iot.bzh>
Mon, 1 Oct 2018 09:51:47 +0000 (11:51 +0200)
committerClément Bénier <clement.benier@iot.bzh>
Mon, 1 Oct 2018 09:57:48 +0000 (11:57 +0200)
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 <clement.benier@iot.bzh>
15 files changed:
Makefile
conf.d/etc/xds/server/server-config.json
lib/xdsconfig/config.go
lib/xdsconfig/fileconfig.go
lib/xdsserver/apiv1-exec.go
lib/xdsserver/apiv1-sdks.go
lib/xdsserver/apiv1-updates.go [new file with mode: 0644]
lib/xdsserver/apiv1.go
lib/xdsserver/webserver.go
lib/xdsserver/xds-server-update.go [new file with mode: 0644]
lib/xdsserver/xdsserver.go
lib/xsapiv1/xds-server-update.go [new file with mode: 0644]
scripts/package-update/getXdsServerPackage [new file with mode: 0755]
scripts/package-update/restartXdsServer [new file with mode: 0755]
scripts/package-update/updateXdsServerPackage [new file with mode: 0755]

index 64ba5a3..8dec007 100644 (file)
--- 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:
index 47b75ef..5d23a6f 100644 (file)
@@ -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",
index 4ba97ef..ab0539c 100644 (file)
@@ -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,
        }
index b786a24..0b248cd 100644 (file)
@@ -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, " ")
index 57ea1f1..c045e9e 100644 (file)
@@ -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
        }
index 7bb9767..2f875ea 100644 (file)
@@ -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 (file)
index 0000000..930ec64
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017-2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * 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")
+}
index e0bfa7f..e394d9e 100644 (file)
@@ -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
 }
index 24456b9..2654caa 100644 (file)
@@ -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 (file)
index 0000000..6fcf9d9
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017-2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * 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
+}
index 1079eba..ba2a560 100644 (file)
@@ -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 (file)
index 0000000..0c3f793
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017-2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * 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 (executable)
index 0000000..e54d09e
--- /dev/null
@@ -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 (executable)
index 0000000..40dfee9
--- /dev/null
@@ -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 (executable)
index 0000000..4f638b8
--- /dev/null
@@ -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
+