Skip to content

Commit

Permalink
Merge pull request #190 from orbs-network/feature/autoupdate
Browse files Browse the repository at this point in the history
Autoupdate
  • Loading branch information
Kirill Maksimov authored Oct 29, 2020
2 parents 652c67a + 23faded commit f884e51
Show file tree
Hide file tree
Showing 16 changed files with 324 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.7.0
v1.8.0
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ In case you ever need to regenerate the SSL certificate:

`--timeout` timeout for provisioning all virtual chains (duration: 1s, 1m, 1h, etc)

`--auto-update` enables boyar binary auto update (default false)

`shutdown-after-update` the process shuts down after automatic update is performed and **DOES NOT** restart; recommended to be used with an external process manager (default false)

`--version` show version, git commit and Docker API version

### SSL options
Expand All @@ -96,6 +100,9 @@ If both these parameters are present, the node will also start service SSL traff
--keys ./e2e-config/node3/keys.json \
--daemonize

It is recommended to run Boyar together with some kind of process manager (for example, [Supervisord](http://supervisord.org)).
If autoupdate is enabled, it becomes crucial if you enable `--shutdown-after-update` feature for seamless automatic updates.

### Print configuration and exit

boyar --config-url https://s3.amazonaws.com/boyar-bootstrap-test/boyar/config.json \
Expand Down
5 changes: 5 additions & 0 deletions boyar/config/config_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ type Flags struct {

ManagementConfig string

// Autoupdate
AutoUpdate bool
ShutdownAfterUpdate bool
BoyarBinaryPath string

// Testing only
WithNamespace bool
}
10 changes: 10 additions & 0 deletions boyar/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/orbs-network/boyarin/version"
"github.com/orbs-network/scribe/log"
"os"
"path/filepath"
"time"
)

Expand Down Expand Up @@ -46,6 +47,9 @@ func main() {

showStatus := flag.Bool("show-status", false, "print status in json format and exit")

autoUpdate := flag.Bool("auto-update", false, "enables boyar binary auto update")
shutdownAfterUpdate := flag.Bool("shutdown-after-update", false, "the process shuts down after automatic update is performed and **DOES NOT** restart; recommended to be used with an external process manager")

flag.Parse()

if *showVersion {
Expand All @@ -67,6 +71,9 @@ func main() {
return
}

executable, _ := os.Executable()
executableWithoutSymlink, _ := filepath.EvalSymlinks(executable)

flags := &config.Flags{
ConfigUrl: *configUrlPtr,
KeyPairConfigPath: *keyPairConfigPathPtr,
Expand All @@ -82,6 +89,9 @@ func main() {
SSLCertificatePath: *sslCertificatePathPtr,
SSLPrivateKeyPath: *sslPrivateKeyPtr,
ManagementConfig: *managementConfig,
AutoUpdate: *autoUpdate,
ShutdownAfterUpdate: *shutdownAfterUpdate,
BoyarBinaryPath: executableWithoutSymlink,
}

if *help {
Expand Down
1 change: 1 addition & 0 deletions build-binaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
export SEMVER=$(cat ./.version)
export GIT_COMMIT=${GIT_COMMIT-$(./.circleci/hash.sh)}
export CONFIG_PKG="github.com/orbs-network/boyarin/version"
export CGO_ENABLED=0

go build -ldflags "-w -extldflags '-static' -X $CONFIG_PKG.SemanticVersion=$SEMVER -X $CONFIG_PKG.CommitVersion=$GIT_COMMIT" -tags "$BUILD_FLAG usergo netgo" -o boyar.bin -a ./boyar/main/main.go
10 changes: 10 additions & 0 deletions crypto/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ package crypto
import (
"crypto/sha256"
"encoding/hex"
"io/ioutil"
)

func CalculateHash(input []byte) string {
checksum := sha256.Sum256(input)
return hex.EncodeToString(checksum[:])
}

func CalculateFileHash(path string) (string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}

return CalculateHash(data), nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/docker/go-units v0.4.0 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gogo/protobuf v1.3.1 // indirect
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
Expand Down
6 changes: 6 additions & 0 deletions services/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package services

import (
"context"
"errors"
"github.com/orbs-network/boyarin/boyar/config"
"github.com/orbs-network/scribe/log"
"io/ioutil"
Expand Down Expand Up @@ -43,5 +44,10 @@ func Bootstrap(ctx context.Context, flags *config.Flags, logger log.Logger) (*co
}

coreBoyar := NewCoreBoyarService(logger)
if shouldExit := coreBoyar.CheckForUpdates(flags, cfg.OrchestratorOptions().ExecutableImage); shouldExit {
logger.Info("shutting down after updating boyar binary")
return flags, errors.New("restart needed after an update")
}

return newFlags, coreBoyar.OnConfigChange(ctx, cfg)
}
18 changes: 14 additions & 4 deletions services/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func Execute(ctx context.Context, flags *config.Flags, logger log.Logger) (govnr
coreBoyar := NewCoreBoyarService(logger)

// wire cfg and boyar
supervisor.Supervise(govnr.Forever(ctx, "apply config changes", utils.NewLogErrors("apply config changes", logger), func() {
ctxWithCancel, cancelAndExit := context.WithCancel(ctx)
supervisor.Supervise(govnr.Forever(ctxWithCancel, "apply config changes", utils.NewLogErrors("apply config changes", logger), func() {
var cfg config.NodeConfiguration = nil
select {
case <-ctx.Done():
Expand All @@ -53,16 +54,25 @@ func Execute(ctx context.Context, flags *config.Flags, logger log.Logger) (govnr
logger.Info("applying new configuration immediately")
}

ctx, cancel := context.WithTimeout(ctx, flags.Timeout)
defer cancel()
if ctx.Err() != nil {
if shouldExit := coreBoyar.CheckForUpdates(flags, cfg.OrchestratorOptions().ExecutableImage); shouldExit {
logger.Info("shutting down after updating boyar binary")
cancelAndExit()
return
}

ctx, cancel := context.WithTimeout(ctx, flags.Timeout)
defer cancel()

err := coreBoyar.OnConfigChange(ctx, cfg)
if err != nil {
logger.Error("error executing configuration", log.Error(err))
cfgFetcher.Resend()
}

if ctx.Err() != nil {
logger.Error("failed to apply new configuration", log.Error(ctx.Err()))
return
}
}))

supervisor.Supervise(cfgFetcher.Start(ctx))
Expand Down
57 changes: 57 additions & 0 deletions services/self_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package services

import (
"encoding/hex"
"fmt"
"github.com/inconshreveable/go-update"
"github.com/orbs-network/boyarin/boyar/config"
"github.com/orbs-network/boyarin/crypto"
"github.com/orbs-network/boyarin/strelets/adapter"
"github.com/orbs-network/scribe/log"
"net/http"
)

func (coreBoyar *BoyarService) SelfUpdate(targetPath string, image adapter.ExecutableImageOptions) error {
checksum, err := hex.DecodeString(image.Sha256)
if err != nil {
return fmt.Errorf("could not decode boyar binary SHA256 checksum \"%s\": %s", image.Sha256, err)
}

coreBoyar.logger.Info("downloading new boyar binary", log.String("url", image.Url))
resp, err := http.Get(image.Url)
if err != nil {
return err
}
defer resp.Body.Close()

return update.Apply(resp.Body, update.Options{
TargetPath: targetPath,
Checksum: checksum,
})
}

func (coreBoyar *BoyarService) CheckForUpdates(flags *config.Flags, options adapter.ExecutableImageOptions) (shouldExit bool) {
shouldExit = false
if flags.AutoUpdate {
currentHash, err := crypto.CalculateFileHash(flags.BoyarBinaryPath)
if err != nil {
coreBoyar.logger.Error("failed to calculate boyar binary hash", log.Error(err))
return
}

if currentHash == options.Sha256 { // already the correct version
return
}

if err := coreBoyar.SelfUpdate(flags.BoyarBinaryPath, options); err != nil {
coreBoyar.logger.Error("failed to update self", log.Error(err))
return
} else {
coreBoyar.logger.Info("successfully replaced boyar binary", log.String("path", flags.BoyarBinaryPath))
}

return flags.ShutdownAfterUpdate
}

return
}
137 changes: 137 additions & 0 deletions services/self_update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package services

import (
"github.com/orbs-network/boyarin/boyar/config"
"github.com/orbs-network/boyarin/crypto"
"github.com/orbs-network/boyarin/strelets/adapter"
"github.com/orbs-network/scribe/log"
"github.com/stretchr/testify/require"
"io/ioutil"
"os"
"path/filepath"
"testing"
)

var VALID_UPDATE_OPTIONS = adapter.ExecutableImageOptions{
Url: "https://github.com/orbs-network/boyarin/releases/download/v1.4.0/boyar-v1.4.0.bin",
Sha256: "1998cc1f7721acfe1954ab2878cc0ad8062cd6d919cd61fa22401c6750e195fe",
}

func TestBoyarService_SelfUpdateHappyFlow(t *testing.T) {
targetPath, _ := prepareSelfUpdateTest(t)

logger := log.GetLogger()
coreBoyar := NewCoreBoyarService(logger)

flags := &config.Flags{
AutoUpdate: true,
BoyarBinaryPath: targetPath,
ShutdownAfterUpdate: true,
}

shouldExit := coreBoyar.CheckForUpdates(flags, VALID_UPDATE_OPTIONS)

require.True(t, shouldExit)
newChecksum, _ := crypto.CalculateFileHash(targetPath)
require.EqualValues(t, VALID_UPDATE_OPTIONS.Sha256, newChecksum)
}

func prepareSelfUpdateTest(t *testing.T) (targetPath string, checksum string) {
os.RemoveAll("./_tmp")
os.MkdirAll("./_tmp", 0755)

targetPath = filepath.Join("./_tmp", "boyar.bin")
err := ioutil.WriteFile(targetPath, []byte("fake binary"), 0755)
require.NoError(t, err)

checksum, err = crypto.CalculateFileHash(targetPath)
require.NoError(t, err)

return targetPath, checksum
}

func TestBoyarService_SelfUpdateWithDisabledAutoUpdate(t *testing.T) {
targetPath, checksum := prepareSelfUpdateTest(t)

logger := log.GetLogger()
coreBoyar := NewCoreBoyarService(logger)

flags := &config.Flags{
AutoUpdate: false,
BoyarBinaryPath: targetPath,
ShutdownAfterUpdate: true,
}

shouldExit := coreBoyar.CheckForUpdates(flags, VALID_UPDATE_OPTIONS)

// nothing changed
require.False(t, shouldExit)
newChecksum, _ := crypto.CalculateFileHash(targetPath)
require.EqualValues(t, checksum, newChecksum)
}

func TestBoyarService_SelfUpdateWithWrongBinaryPath(t *testing.T) {
targetPath, checksum := prepareSelfUpdateTest(t)

logger := log.GetLogger()
coreBoyar := NewCoreBoyarService(logger)

flags := &config.Flags{
AutoUpdate: true,
BoyarBinaryPath: targetPath + "0",
ShutdownAfterUpdate: true,
}

shouldExit := coreBoyar.CheckForUpdates(flags, VALID_UPDATE_OPTIONS)

// nothing changed
require.False(t, shouldExit)
newChecksum, _ := crypto.CalculateFileHash(targetPath)
require.EqualValues(t, checksum, newChecksum)
}

func TestBoyarService_SelfUpdateWithMalformedUrl(t *testing.T) {
targetPath, checksum := prepareSelfUpdateTest(t)

logger := log.GetLogger()
coreBoyar := NewCoreBoyarService(logger)

flags := &config.Flags{
AutoUpdate: true,
BoyarBinaryPath: targetPath,
ShutdownAfterUpdate: true,
}

shouldExit := coreBoyar.CheckForUpdates(flags, adapter.ExecutableImageOptions{
Url: "http://localhost/does-not-exist",
Sha256: VALID_UPDATE_OPTIONS.Sha256,
})

// nothing changed
require.False(t, shouldExit)
newChecksum, _ := crypto.CalculateFileHash(targetPath)
require.EqualValues(t, checksum, newChecksum)
}

func TestBoyarService_SelfUpdateWithWrongChecksum(t *testing.T) {
targetPath, checksum := prepareSelfUpdateTest(t)

logger := log.GetLogger()
coreBoyar := NewCoreBoyarService(logger)

flags := &config.Flags{
AutoUpdate: true,
BoyarBinaryPath: targetPath,
ShutdownAfterUpdate: true,
}

shouldExit := coreBoyar.CheckForUpdates(flags, adapter.ExecutableImageOptions{
Url: VALID_UPDATE_OPTIONS.Url,
Sha256: "0000cc1f7721acfe1954ab2878cc0ad8062cd6d919cd61fa22401c6750e195fe",
})

// nothing changed
require.False(t, shouldExit)
newChecksum, _ := crypto.CalculateFileHash(targetPath)
require.EqualValues(t, checksum, newChecksum)
}
7 changes: 7 additions & 0 deletions strelets/adapter/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ type DynamicManagementConfig struct {
Url string
}

type ExecutableImageOptions struct {
Url string
Sha256 string
}

type OrchestratorOptions struct {
StorageDriver string `json:"storage-driver"`
StorageMountType string `json:"storage-mount-type"`
Expand All @@ -86,6 +91,8 @@ type OrchestratorOptions struct {

DynamicManagementConfig DynamicManagementConfig

ExecutableImage ExecutableImageOptions

// Testing purposes
HTTPPort uint32 `json:"http-port"`
SSLPort uint32 `json:"ssl-port"`
Expand Down
Loading

0 comments on commit f884e51

Please sign in to comment.