From 015350e230a9aa832c6e90632f1b28f4f28a7552 Mon Sep 17 00:00:00 2001 From: Michal Niewrzal Date: Thu, 31 Oct 2019 05:27:53 -0700 Subject: [PATCH] storagenode-updater: add autoupdating (#3422) --- cmd/storagenode-updater/main.go | 166 ++++++++++++----- cmd/storagenode-updater/main_test.go | 261 ++++++++++++++++++++------- internal/testcontext/compile.go | 37 +++- internal/version/checker/client.go | 6 +- internal/version/version.go | 11 +- internal/version/version_test.go | 6 +- versioncontrol/peer.go | 16 +- 7 files changed, 380 insertions(+), 123 deletions(-) diff --git a/cmd/storagenode-updater/main.go b/cmd/storagenode-updater/main.go index aaee2836b..d86c5db81 100644 --- a/cmd/storagenode-updater/main.go +++ b/cmd/storagenode-updater/main.go @@ -9,6 +9,7 @@ import ( "bytes" "context" "errors" + "fmt" "io" "io/ioutil" "log" @@ -37,10 +38,15 @@ import ( "storj.io/storj/pkg/storj" ) -const minCheckInterval = time.Minute +const ( + updaterServiceName = "storagenode-updater" + minCheckInterval = time.Minute +) var ( cancel context.CancelFunc + // TODO: replace with config value of random bytes in storagenode config. + nodeID storj.NodeID rootCmd = &cobra.Command{ Use: "storagenode-updater", @@ -60,13 +66,16 @@ var ( BinaryLocation string `help:"the storage node executable binary location" default:"storagenode.exe"` ServiceName string `help:"storage node OS service name" default:"storagenode"` - Log string `help:"path to log file, if empty standard output will be used" default:""` + // NB: can't use `log.output` because windows service command args containing "." are bugged. + Log string `help:"path to log file, if empty standard output will be used" default:""` } confDir string identityDir string ) +type renameFunc func(currentVersion version.SemVer) error + func init() { // TODO: this will probably generate warnings for mismatched config fields. defaultConfDir := fpath.ApplicationDir("storj", "storagenode") @@ -81,14 +90,8 @@ func init() { } func cmdRun(cmd *cobra.Command, args []string) (err error) { - if runCfg.Log != "" { - logFile, err := os.OpenFile(runCfg.Log, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - log.Fatalf("error opening log file: %s", err) - } - defer func() { err = errs.Combine(err, logFile.Close()) }() - log.SetOutput(logFile) - } + closeLog, err := openLog() + defer func() { err = errs.Combine(err, closeLog()) }() if !fileExists(runCfg.BinaryLocation) { log.Fatal("unable to find storage node executable binary") @@ -98,9 +101,13 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { if err != nil { log.Fatalf("error loading identity: %s", err) } + nodeID = ident.ID + if nodeID.IsZero() { + log.Fatal("empty node ID") + } var ctx context.Context - ctx, cancel = context.WithCancel(context.Background()) + ctx, cancel = process.Ctx(cmd) c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) @@ -112,7 +119,13 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { }() loopFunc := func(ctx context.Context) (err error) { - if err := update(ctx, ident.ID); err != nil { + if err := update(ctx, runCfg.BinaryLocation, runCfg.ServiceName, renameStoragenode); err != nil { + // don't finish loop in case of error just wait for another execution + log.Println(err) + } + + updaterBinName := os.Args[0] + if err := update(ctx, updaterBinName, updaterServiceName, renameUpdater); err != nil { // don't finish loop in case of error just wait for another execution log.Println(err) } @@ -136,24 +149,30 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { return nil } -func update(ctx context.Context, nodeID storj.NodeID) (err error) { - client := checker.New(runCfg.ClientConfig) - - currentVersion, err := binaryVersion(runCfg.BinaryLocation) - if err != nil { - return errs.Wrap(err) +func update(ctx context.Context, binPath, serviceName string, renameBinary renameFunc) (err error) { + if nodeID.IsZero() { + log.Fatal("empty node ID") } + var currentVersion version.SemVer + if serviceName == updaterServiceName { + // TODO: find better way to check this binary version + currentVersion = version.Build.Version + } else { + currentVersion, err = binaryVersion(binPath) + if err != nil { + return errs.Wrap(err) + } + } + + client := checker.New(runCfg.ClientConfig) log.Println("downloading versions from", runCfg.ServerAddress) - shouldUpdate, newVersion, err := client.ShouldUpdate(ctx, runCfg.ServiceName, nodeID) + shouldUpdate, newVersion, err := client.ShouldUpdate(ctx, serviceName, nodeID) if err != nil { return errs.Wrap(err) } if shouldUpdate { - downloadURL := newVersion.URL - downloadURL = strings.Replace(downloadURL, "{os}", runtime.GOOS, 1) - downloadURL = strings.Replace(downloadURL, "{arch}", runtime.GOARCH, 1) // TODO: consolidate semver.Version and version.SemVer suggestedVersion, err := newVersion.SemVer() if err != nil { @@ -161,12 +180,13 @@ func update(ctx context.Context, nodeID storj.NodeID) (err error) { } if currentVersion.Compare(suggestedVersion) < 0 { - tempArchive, err := ioutil.TempFile(os.TempDir(), runCfg.ServiceName) + tempArchive, err := ioutil.TempFile(os.TempDir(), serviceName) if err != nil { return errs.New("cannot create temporary archive: %v", err) } defer func() { err = errs.Combine(err, os.Remove(tempArchive.Name())) }() + downloadURL := parseDownloadURL(newVersion.URL) log.Println("start downloading", downloadURL, "to", tempArchive.Name()) err = downloadArchive(ctx, tempArchive, downloadURL) if err != nil { @@ -174,24 +194,19 @@ func update(ctx context.Context, nodeID storj.NodeID) (err error) { } log.Println("finished downloading", downloadURL, "to", tempArchive.Name()) - extension := filepath.Ext(runCfg.BinaryLocation) - if extension != "" { - extension = "." + extension - } - - dir := filepath.Dir(runCfg.BinaryLocation) - backupExec := filepath.Join(dir, runCfg.ServiceName+".old."+currentVersion.String()+extension) - - if err = os.Rename(runCfg.BinaryLocation, backupExec); err != nil { - return errs.Wrap(err) - } - - err = unpackBinary(ctx, tempArchive.Name(), runCfg.BinaryLocation) + err = renameBinary(currentVersion) if err != nil { return errs.Wrap(err) } - downloadedVersion, err := binaryVersion(runCfg.BinaryLocation) + err = unpackBinary(ctx, tempArchive.Name(), binPath) + if err != nil { + return errs.Wrap(err) + } + + // TODO add here recovery even before starting service (if version command cannot be executed) + + downloadedVersion, err := binaryVersion(binPath) if err != nil { return errs.Wrap(err) } @@ -200,13 +215,13 @@ func update(ctx context.Context, nodeID storj.NodeID) (err error) { return errs.New("invalid version downloaded: wants %s got %s", suggestedVersion.String(), downloadedVersion.String()) } - log.Println("restarting service", runCfg.ServiceName) - err = restartSNService(runCfg.ServiceName) + log.Println("restarting service", serviceName) + err = restartService(serviceName) if err != nil { // TODO: should we try to recover from this? return errs.New("unable to restart service: %v", err) } - log.Println("service", runCfg.ServiceName, "restarted successfully") + log.Println("service", serviceName, "restarted successfully") // TODO remove old binary ?? return nil @@ -217,9 +232,41 @@ func update(ctx context.Context, nodeID storj.NodeID) (err error) { return nil } +func renameStoragenode(currentVersion version.SemVer) error { + extension := filepath.Ext(runCfg.BinaryLocation) + dir := filepath.Dir(runCfg.BinaryLocation) + backupExec := filepath.Join(dir, runCfg.ServiceName+".old."+currentVersion.String()+extension) + + if err := os.Rename(runCfg.BinaryLocation, backupExec); err != nil { + return errs.Wrap(err) + } + return nil +} + +func renameUpdater(_ version.SemVer) error { + updaterBinName := os.Args[0] + extension := filepath.Ext(updaterBinName) + dir := filepath.Dir(updaterBinName) + base := filepath.Base(updaterBinName) + base = base[:len(base)-len(extension)] + backupExec := filepath.Join(dir, base+".old"+extension) + + if err := os.Rename(updaterBinName, backupExec); err != nil { + return errs.Wrap(err) + } + return nil +} + +func parseDownloadURL(template string) string { + url := strings.Replace(template, "{os}", runtime.GOOS, 1) + url = strings.Replace(url, "{arch}", runtime.GOARCH, 1) + return url +} + func binaryVersion(location string) (version.SemVer, error) { - out, err := exec.Command(location, "version").Output() + out, err := exec.Command(location, "version").CombinedOutput() if err != nil { + log.Printf("command output: %s", out) return version.SemVer{}, err } @@ -283,16 +330,26 @@ func unpackBinary(ctx context.Context, archive, target string) (err error) { return nil } -func restartSNService(name string) error { +func restartService(name string) error { switch runtime.GOOS { case "windows": - // TODO how run this as one command `net stop servicename && net start servicename`? // TODO: combine stdout with err if err - _, err := exec.Command("net", "stop", name).CombinedOutput() + restartSvcBatPath := filepath.Join(os.TempDir(), "restartservice.bat") + restartSvcBat, err := os.Create(restartSvcBatPath) if err != nil { return err } - _, err = exec.Command("net", "start", name).CombinedOutput() + + restartStr := fmt.Sprintf("net stop %s && net start %s", name, name) + _, err = restartSvcBat.WriteString(restartStr) + if err != nil { + return err + } + if err := restartSvcBat.Close(); err != nil { + return err + } + + _, err = exec.Command(restartSvcBat.Name()).CombinedOutput() if err != nil { return err } @@ -311,5 +368,22 @@ func fileExists(filename string) bool { } func main() { - _ = rootCmd.Execute() + process.Exec(rootCmd) +} + +// TODO: improve logging; other commands use zap but due to an apparent +// windows bug we're unable to use the existing process logging infrastructure. +func openLog() (closeFunc func() error, err error) { + closeFunc = func() error { return nil } + + if runCfg.Log != "" { + logFile, err := os.OpenFile(runCfg.Log, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Printf("error opening log file: %s", err) + return closeFunc, err + } + log.SetOutput(logFile) + return logFile.Close, nil + } + return closeFunc, nil } diff --git a/cmd/storagenode-updater/main_test.go b/cmd/storagenode-updater/main_test.go index d6944aa46..02b44e170 100644 --- a/cmd/storagenode-updater/main_test.go +++ b/cmd/storagenode-updater/main_test.go @@ -4,68 +4,197 @@ package main_test import ( + "archive/zip" + "fmt" + "io" "io/ioutil" "net/http" "net/http/httptest" "os" "os/exec" + "path/filepath" + "runtime" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "storj.io/storj/internal/testcontext" + "storj.io/storj/internal/testidentity" + "storj.io/storj/internal/version" + "storj.io/storj/pkg/identity" + "storj.io/storj/pkg/storj" "storj.io/storj/versioncontrol" ) -func TestAutoUpdater(t *testing.T) { +const ( + oldVersion = "v0.19.0" + newVersion = "v0.19.5" +) + +func TestAutoUpdater_unix(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("requires storagenode and storagenode-updater to be installed as windows services") + } + + // TODO cleanup `.exe` extension for different OS + ctx := testcontext.New(t) defer ctx.Cleanup() - testFiles := []struct { - src string - dst string - perms os.FileMode - }{ - { - "testdata/fake-storagenode", - ctx.File("storagenode"), - 0755, - }, - { - "testdata/fake-ident.cert", - ctx.File("identity.cert"), - 0644, - }, - { - "testdata/fake-ident.key", - ctx.File("identity.key"), - 0600, - }, + oldSemVer, err := version.NewSemVer(oldVersion) + require.NoError(t, err) + + newSemVer, err := version.NewSemVer(newVersion) + require.NoError(t, err) + + oldInfo := version.Info{ + Timestamp: time.Now(), + CommitHash: "", + Version: oldSemVer, + Release: false, } - for _, file := range testFiles { - content, err := ioutil.ReadFile(file.src) - require.NoError(t, err) + // build real bin with old version, will be used for both storagenode and updater + oldBin := ctx.CompileWithVersion("", oldInfo) + storagenodePath := ctx.File("fake", "storagenode.exe") + copy(ctx, t, oldBin, storagenodePath) - err = ioutil.WriteFile(file.dst, content, file.perms) - require.NoError(t, err) + updaterPath := ctx.File("fake", "storagenode-updater.exe") + move(t, oldBin, updaterPath) + + // build real storagenode and updater with new version + newInfo := version.Info{ + Timestamp: time.Now(), + CommitHash: "", + Version: newSemVer, + Release: false, } + newBin := ctx.CompileWithVersion("", newInfo) + updateBins := map[string]string{ + "storagenode": newBin, + "storagenode-updater": newBin, + } + + // run versioncontrol and update zips http servers + versionControlPeer, cleanupVersionControl := testVersionControlWithUpdates(ctx, t, updateBins) + defer cleanupVersionControl() + + logPath := ctx.File("storagenode-updater.log") + + // write identity files to disk for use in rollout calculation + identConfig := testIdentityFiles(ctx, t) + + // run updater (update) + args := []string{"run"} + args = append(args, "--config-dir", ctx.Dir()) + args = append(args, "--server-address", "http://"+versionControlPeer.Addr()) + args = append(args, "--binary-location", storagenodePath) + args = append(args, "--check-interval", "0s") + args = append(args, "--identity.cert-path", identConfig.CertPath) + args = append(args, "--identity.key-path", identConfig.KeyPath) + args = append(args, "--log", logPath) + + // NB: updater currently uses `log.SetOutput` so all output after that call + // only goes to the log file. + out, err := exec.Command(updaterPath, args...).CombinedOutput() + logData, logErr := ioutil.ReadFile(logPath) + if assert.NoError(t, logErr) { + logStr := string(logData) + t.Log(logStr) + if !assert.Contains(t, logStr, "storagenode restarted successfully") { + t.Log(logStr) + } + if !assert.Contains(t, logStr, "storagenode-updater restarted successfully") { + t.Log(logStr) + } + } else { + t.Log(string(out)) + } + if !assert.NoError(t, err) { + t.FailNow() + } + + oldStoragenode := ctx.File("fake", "storagenode"+".old."+oldVersion+".exe") + oldStoragenodeInfo, err := os.Stat(oldStoragenode) + require.NoError(t, err) + require.NotNil(t, oldStoragenodeInfo) + require.NotZero(t, oldStoragenodeInfo.Size()) + + backupUpdater := ctx.File("fake", "storagenode-updater.old.exe") + backupUpdaterInfo, err := os.Stat(backupUpdater) + require.NoError(t, err) + require.NotNil(t, backupUpdaterInfo) + require.NotZero(t, backupUpdaterInfo.Size()) +} + +func move(t *testing.T, src, dst string) { + err := os.Rename(src, dst) + require.NoError(t, err) +} + +func copy(ctx *testcontext.Context, t *testing.T, src, dst string) { + s, err := os.Open(src) + require.NoError(t, err) + defer ctx.Check(s.Close) + + d, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0755) + require.NoError(t, err) + defer ctx.Check(d.Close) + + _, err = io.Copy(d, s) + require.NoError(t, err) +} + +func testIdentityFiles(ctx *testcontext.Context, t *testing.T) identity.Config { + t.Helper() + + ident, err := testidentity.PregeneratedIdentity(0, storj.LatestIDVersion()) + require.NoError(t, err) + + identConfig := identity.Config{ + CertPath: ctx.File("identity", "identity.cert"), + KeyPath: ctx.File("identity", "identity.Key"), + } + err = identConfig.Save(ident) + require.NoError(t, err) + + configData := fmt.Sprintf( + "identity.cert-path: %s\nidentity.key-path: %s", + identConfig.CertPath, + identConfig.KeyPath, + ) + err = ioutil.WriteFile(ctx.File("config.yaml"), []byte(configData), 0644) + require.NoError(t, err) + + return identConfig +} + +func testVersionControlWithUpdates(ctx *testcontext.Context, t *testing.T, updateBins map[string]string) (peer *versioncontrol.Peer, cleanup func()) { + t.Helper() var mux http.ServeMux - content, err := ioutil.ReadFile("testdata/fake-storagenode.zip") - require.NoError(t, err) - mux.HandleFunc("/download", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write(content) + for name, src := range updateBins { + dst := ctx.File("updates", name+".zip") + zipBin(ctx, t, dst, src) + zipData, err := ioutil.ReadFile(dst) require.NoError(t, err) - })) + + mux.HandleFunc("/"+name, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write(zipData) + require.NoError(t, err) + })) + } ts := httptest.NewServer(&mux) - defer ts.Close() config := &versioncontrol.Config{ - Address: "127.0.0.1:0", + // TODO: add STORJ_VERSION_SERVER_ADDR property to Product.wxs for testing + // TODO: set address back to `127.0.0.1:0` + Address: "127.0.0.1:10000", + // NB: this config field is required for versioncontrol to run. Versions: versioncontrol.OldVersionConfig{ Satellite: "v0.0.1", Storagenode: "v0.0.1", @@ -73,11 +202,22 @@ func TestAutoUpdater(t *testing.T) { Gateway: "v0.0.1", Identity: "v0.0.1", }, + // TODO use random seed Binary: versioncontrol.ProcessesConfig{ Storagenode: versioncontrol.ProcessConfig{ Suggested: versioncontrol.VersionConfig{ - Version: "0.19.5", - URL: ts.URL + "/download", + Version: newVersion, + URL: ts.URL + "/storagenode", + }, + Rollout: versioncontrol.RolloutConfig{ + Seed: "0000000000000000000000000000000000000000000000000000000000000001", + Cursor: 100, + }, + }, + StoragenodeUpdater: versioncontrol.ProcessConfig{ + Suggested: versioncontrol.VersionConfig{ + Version: newVersion, + URL: ts.URL + "/storagenode-updater", }, Rollout: versioncontrol.RolloutConfig{ Seed: "0000000000000000000000000000000000000000000000000000000000000001", @@ -91,31 +231,30 @@ func TestAutoUpdater(t *testing.T) { ctx.Go(func() error { return peer.Run(ctx) }) - defer ctx.Check(peer.Close) - - args := make([]string, 0) - args = append(args, "run") - args = append(args, "main.go") - args = append(args, "run") - args = append(args, "--server-address") - args = append(args, "http://"+peer.Addr()) - args = append(args, "--binary-location") - args = append(args, testFiles[0].dst) - args = append(args, "--check-interval") - args = append(args, "0s") - args = append(args, "--identity.cert-path") - args = append(args, testFiles[1].dst) - args = append(args, "--identity.key-path") - args = append(args, testFiles[2].dst) - - out, err := exec.Command("go", args...).CombinedOutput() - result := string(out) - if !assert.NoError(t, err) { - t.Log(result) - t.Fatal(err) - } - - if !assert.Contains(t, result, "restarted successfully") { - t.Log(result) + return peer, func() { + ts.Close() + ctx.Check(peer.Close) } } + +func zipBin(ctx *testcontext.Context, t *testing.T, dst, src string) { + t.Helper() + + zipFile, err := os.Create(dst) + require.NoError(t, err) + + base := filepath.Base(dst) + base = base[:len(base)-len(".zip")] + + writer := zip.NewWriter(zipFile) + defer ctx.Check(writer.Close) + + contents, err := writer.Create(base) + require.NoError(t, err) + + data, err := ioutil.ReadFile(src) + require.NoError(t, err) + + _, err = contents.Write(data) + require.NoError(t, err) +} diff --git a/internal/testcontext/compile.go b/internal/testcontext/compile.go index db86c8dc8..85f37c9e7 100644 --- a/internal/testcontext/compile.go +++ b/internal/testcontext/compile.go @@ -9,7 +9,10 @@ import ( "path" "path/filepath" "runtime" + "strconv" "testing" + + "storj.io/storj/internal/version" ) // CLibMath is the standard C math library (see `man math.h`). @@ -24,7 +27,7 @@ type CompileCOptions struct { } // Compile compiles the specified package and returns the executable name. -func (ctx *Context) Compile(pkg string) string { +func (ctx *Context) Compile(pkg string, preArgs ...string) string { ctx.test.Helper() var binName string @@ -37,7 +40,7 @@ func (ctx *Context) Compile(pkg string) string { exe := ctx.File("build", binName+".exe") - args := []string{"build"} + args := append([]string{"build"}, preArgs...) if raceEnabled { args = append(args, "-race") } @@ -58,6 +61,36 @@ func (ctx *Context) Compile(pkg string) string { return exe } +// CompileWithVersion compiles the specified package with the version variables set +// to the passed version info values and returns the executable name. +func (ctx *Context) CompileWithVersion(pkg string, info version.Info) string { + ctx.test.Helper() + + ldFlagsX := map[string]string{ + "storj.io/storj/internal/version.buildTimestamp": strconv.Itoa(int(info.Timestamp.Unix())), + "storj.io/storj/internal/version.buildCommitHash": info.CommitHash, + "storj.io/storj/internal/version.buildVersion": info.Version.String(), + "storj.io/storj/internal/version.buildRelease": strconv.FormatBool(info.Release), + } + + return ctx.CompileWithLDFlagsX(pkg, ldFlagsX) +} + +// CompileWithLDFlagsX compiles the specified package with the -ldflags flag set to +// "-s -w [-X =,...]" given the passed map and returns the executable name. +func (ctx *Context) CompileWithLDFlagsX(pkg string, ldFlagsX map[string]string) string { + ctx.test.Helper() + + var ldFlags = "-s -w" + if ldFlagsX != nil { + for key, value := range ldFlagsX { + ldFlags += (" -X " + key + "=" + value) + } + } + + return ctx.Compile(pkg, "-ldflags", ldFlags) +} + // CompileShared compiles pkg as c-shared. // TODO: support inclusion from other directories // (cgo header paths are currently relative to package root) diff --git a/internal/version/checker/client.go b/internal/version/checker/client.go index 47b94c1c6..d8c7171cb 100644 --- a/internal/version/checker/client.go +++ b/internal/version/checker/client.go @@ -110,7 +110,7 @@ func (client *Client) Process(ctx context.Context, processName string) (process } processesValue := reflect.ValueOf(versions.Processes) - field := processesValue.FieldByName(strings.Title(processName)) + field := processesValue.FieldByName(kebabToPascal(processName)) processNameErr := Error.New("invalid process name: %s\n", processName) if field == (reflect.Value{}) { @@ -140,3 +140,7 @@ func (client *Client) ShouldUpdate(ctx context.Context, processName string, node } return false, version.Version{}, nil } + +func kebabToPascal(str string) string { + return strings.ReplaceAll(strings.Title(str), "-", "") +} diff --git a/internal/version/version.go b/internal/version/version.go index 49ba7a38e..eeba7b3ba 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -80,11 +80,12 @@ type AllowedVersions struct { // Processes describes versions for each binary. // TODO: this name is inconsistent with the versioncontrol server pkg's analogue, `Versions`. type Processes struct { - Satellite Process `json:"satellite"` - Storagenode Process `json:"storagenode"` - Uplink Process `json:"uplink"` - Gateway Process `json:"gateway"` - Identity Process `json:"identity"` + Satellite Process `json:"satellite"` + Storagenode Process `json:"storagenode"` + StoragenodeUpdater Process `json:"storagenode-updater"` + Uplink Process `json:"uplink"` + Gateway Process `json:"gateway"` + Identity Process `json:"identity"` } // Process versions for specific binary. diff --git a/internal/version/version_test.go b/internal/version/version_test.go index a802d3e51..20761b8c0 100644 --- a/internal/version/version_test.go +++ b/internal/version/version_test.go @@ -115,7 +115,7 @@ func TestShouldUpdate(t *testing.T) { tolerance := total * 2 / 100 // 2% for p := 10; p < 100; p += 10 { - var updates int + var rollouts int percentage := p cursor := version.PercentageToCursor(percentage) @@ -131,12 +131,12 @@ func TestShouldUpdate(t *testing.T) { require.NoError(t, err) if version.ShouldUpdate(rollout, nodeID) { - updates++ + rollouts++ } } assert.Condition(t, func() bool { - diff := updates - (total * percentage / 100) + diff := rollouts - (total * percentage / 100) return int(math.Abs(float64(diff))) < tolerance }) } diff --git a/versioncontrol/peer.go b/versioncontrol/peer.go index 9b6f7ca50..106ded8b6 100644 --- a/versioncontrol/peer.go +++ b/versioncontrol/peer.go @@ -49,11 +49,12 @@ type OldVersionConfig struct { // ProcessesConfig represents versions configuration for all processes. type ProcessesConfig struct { - Satellite ProcessConfig - Storagenode ProcessConfig - Uplink ProcessConfig - Gateway ProcessConfig - Identity ProcessConfig + Satellite ProcessConfig + Storagenode ProcessConfig + StoragenodeUpdater ProcessConfig + Uplink ProcessConfig + Gateway ProcessConfig + Identity ProcessConfig } // ProcessConfig represents versions configuration for a single process. @@ -161,6 +162,11 @@ func New(log *zap.Logger, config *Config) (peer *Peer, err error) { return nil, RolloutErr.Wrap(err) } + peer.Versions.Processes.StoragenodeUpdater, err = configToProcess(config.Binary.StoragenodeUpdater) + if err != nil { + return nil, RolloutErr.Wrap(err) + } + peer.Versions.Processes.Uplink, err = configToProcess(config.Binary.Uplink) if err != nil { return nil, RolloutErr.Wrap(err)