Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement endpoint for checking if visor update is available #217

Merged
merged 3 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions pkg/hypervisor/hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/SkycoinProject/skywire-mainnet/internal/skyenv"
"github.com/SkycoinProject/skywire-mainnet/pkg/app"
"github.com/SkycoinProject/skywire-mainnet/pkg/routing"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo"
"github.com/SkycoinProject/skywire-mainnet/pkg/visor"
)

Expand Down Expand Up @@ -176,6 +177,7 @@ func (hv *Hypervisor) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.Post("/visors/{pk}/restart", hv.restart())
r.Post("/visors/{pk}/exec", hv.exec())
r.Post("/visors/{pk}/update", hv.update())
r.Get("/visors/{pk}/update/available", hv.updateAvailable())
})
})

Expand Down Expand Up @@ -734,12 +736,42 @@ func (hv *Hypervisor) exec() http.HandlerFunc {

func (hv *Hypervisor) update() http.HandlerFunc {
return hv.withCtx(hv.visorCtx, func(w http.ResponseWriter, r *http.Request, ctx *httpCtx) {
if err := ctx.RPC.Update(); err != nil {
updated, err := ctx.RPC.Update()
if err != nil {
httputil.WriteJSON(w, r, http.StatusInternalServerError, err)
return
}

httputil.WriteJSON(w, r, http.StatusOK, true)
output := struct {
Updated bool `json:"updated"`
}{updated}

httputil.WriteJSON(w, r, http.StatusOK, output)
})
}

func (hv *Hypervisor) updateAvailable() http.HandlerFunc {
return hv.withCtx(hv.visorCtx, func(w http.ResponseWriter, r *http.Request, ctx *httpCtx) {
version, err := ctx.RPC.UpdateAvailable()
if err != nil {
httputil.WriteJSON(w, r, http.StatusInternalServerError, err)
return
}

output := struct {
Available bool `json:"available"`
CurrentVersion string `json:"current_version"`
AvailableVersion string `json:"available_version,omitempty"`
}{
Available: version != nil,
CurrentVersion: buildinfo.Version(),
}

if version != nil {
output.AvailableVersion = version.String()
}

httputil.WriteJSON(w, r, http.StatusOK, output)
})
}

Expand Down
62 changes: 39 additions & 23 deletions pkg/util/updater/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,36 +71,31 @@ func New(log *logging.Logger, restartCtx *restart.Context, appsPath string) *Upd

// Update performs an update operation.
// NOTE: Update may call os.Exit.
func (u *Updater) Update() (err error) {
func (u *Updater) Update() (updated bool, err error) {
if !atomic.CompareAndSwapInt32(&u.updating, 0, 1) {
return ErrAlreadyStarted
return false, ErrAlreadyStarted
}
defer atomic.StoreInt32(&u.updating, 0)

u.log.Infof("Looking for updates")

lastVersion, err := lastVersion()
latestVersion, err := u.UpdateAvailable()
if err != nil {
return fmt.Errorf("failed to get last Skywire version: %w", err)
return false, fmt.Errorf("failed to get last Skywire version: %w", err)
}

u.log.Infof("Last Skywire version: %q", lastVersion.String())

if !updateAvailable(lastVersion) {
u.log.Infof("You are using the latest version of Skywire")
return nil
if latestVersion == nil {
return false, nil
}

u.log.Infof("Update found, version: %q", lastVersion.String())
u.log.Infof("Update found, version: %q", latestVersion.String())

downloadedBinariesPath, err := u.download(lastVersion.String())
downloadedBinariesPath, err := u.download(latestVersion.String())
if err != nil {
return err
return false, err
}

currentBasePath := filepath.Dir(u.restartCtx.CmdPath())
if err := u.updateBinaries(downloadedBinariesPath, currentBasePath); err != nil {
return err
return false, err
}

if err := u.restartCurrentProcess(); err != nil {
Expand All @@ -109,7 +104,7 @@ func (u *Updater) Update() (err error) {

u.restore(currentVisorPath, oldVisorPath)

return err
return false, err
}

u.removeFiles(downloadedBinariesPath)
Expand All @@ -121,7 +116,28 @@ func (u *Updater) Update() (err error) {
}
}()

return nil
return true, nil
}

// UpdateAvailable checks if an update is available.
// If it is, the method returns the last available version.
// Otherwise, it returns nil.
func (u *Updater) UpdateAvailable() (*Version, error) {
u.log.Infof("Looking for updates")

latestVersion, err := latestVersion()
if err != nil {
return nil, err
}

u.log.Infof("Last Skywire version: %q", latestVersion.String())

if !needUpdate(latestVersion) {
u.log.Infof("You are using the latest version of Skywire")
return nil, nil
}

return latestVersion, nil
}

func (u *Updater) exitAfterDelay(delay time.Duration) {
Expand Down Expand Up @@ -392,7 +408,7 @@ func archiveFilename(file, version, os, arch string) string {
return file + "-" + version + "-" + os + "-" + arch + archiveFormat
}

func updateAvailable(last *Version) bool {
func needUpdate(last *Version) bool {
current, err := currentVersion()
if err != nil {
// Unknown versions should be updated.
Expand All @@ -402,20 +418,20 @@ func updateAvailable(last *Version) bool {
return last.Cmp(current) > 0
}

func lastVersion() (*Version, error) {
html, err := lastVersionHTML()
func latestVersion() (*Version, error) {
html, err := latestVersionHTML()
if err != nil {
return nil, err
}

return VersionFromString(extractLastVersion(string(html)))
return VersionFromString(extractLatestVersion(string(html)))
}

func currentVersion() (*Version, error) {
return VersionFromString(buildinfo.Version())
}

func lastVersionHTML() (data []byte, err error) {
func latestVersionHTML() (data []byte, err error) {
resp, err := http.Get(releaseURL)
if err != nil {
return nil, err
Expand All @@ -430,7 +446,7 @@ func lastVersionHTML() (data []byte, err error) {
return ioutil.ReadAll(resp.Body)
}

func extractLastVersion(buffer string) string {
func extractLatestVersion(buffer string) string {
// First occurrence is the latest version.
idx := strings.Index(buffer, urlText)
if idx == -1 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/util/updater/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
)

func Test_extractLastVersion(t *testing.T) {
func Test_extractLatestVersion(t *testing.T) {
tests := []struct {
name string
buffer string
Expand All @@ -32,7 +32,7 @@ func Test_extractLastVersion(t *testing.T) {
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := extractLastVersion(tc.buffer)
got := extractLatestVersion(tc.buffer)
assert.Equal(t, tc.want, got)
})
}
Expand Down
25 changes: 22 additions & 3 deletions pkg/visor/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/SkycoinProject/skywire-mainnet/pkg/transport"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/rpcutil"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/updater"
)

const (
Expand Down Expand Up @@ -499,8 +500,26 @@ func (r *RPC) Exec(cmd *string, out *[]byte) (err error) {
}

// Update updates visor.
func (r *RPC) Update(_ *struct{}, _ *struct{}) (err error) {
defer rpcutil.LogCall(r.log, "Update", nil)(nil, &err)
func (r *RPC) Update(_ *struct{}, updated *bool) (err error) {
defer rpcutil.LogCall(r.log, "Update", nil)(updated, &err)

return r.visor.Update()
*updated, err = r.visor.Update()
return
}

// UpdateAvailable checks if visor update is available.
func (r *RPC) UpdateAvailable(_ *struct{}, version *updater.Version) (err error) {
defer rpcutil.LogCall(r.log, "UpdateAvailable", nil)(version, &err)

v, err := r.visor.UpdateAvailable()
if err != nil {
return err
}

if v == nil {
return nil
}

*version = *v
return nil
}
37 changes: 30 additions & 7 deletions pkg/visor/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/SkycoinProject/skywire-mainnet/pkg/snet/snettest"
"github.com/SkycoinProject/skywire-mainnet/pkg/transport"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/updater"
)

var (
Expand Down Expand Up @@ -61,7 +62,8 @@ type RPCClient interface {

Restart() error
Exec(command string) ([]byte, error)
Update() error
Update() (bool, error)
UpdateAvailable() (*updater.Version, error)
}

// RPCClient provides methods to call an RPC Server.
Expand Down Expand Up @@ -250,9 +252,25 @@ func (rc *rpcClient) Exec(command string) ([]byte, error) {
}

// Update calls Update.
func (rc *rpcClient) Update() error {
err := rc.Call("Update", &struct{}{}, &struct{}{})
return err
func (rc *rpcClient) Update() (bool, error) {
var updated bool
err := rc.Call("Update", &struct{}{}, &updated)
return updated, err
}

// UpdateAvailable calls UpdateAvailable.
func (rc *rpcClient) UpdateAvailable() (*updater.Version, error) {
var version, empty updater.Version
err := rc.Call("UpdateAvailable", &struct{}{}, &version)
if err != nil {
return nil, err
}

if version == empty {
return nil, nil
}

return &version, err
}

// MockRPCClient mocks RPCClient.
Expand Down Expand Up @@ -614,7 +632,12 @@ func (mc *mockRPCClient) Exec(string) ([]byte, error) {
return []byte("mock"), nil
}

// Exec implements RPCClient.
func (mc *mockRPCClient) Update() error {
return nil
// Update implements RPCClient.
func (mc *mockRPCClient) Update() (bool, error) {
return false, nil
}

// UpdateAvailable implements RPCClient.
func (mc *mockRPCClient) UpdateAvailable() (*updater.Version, error) {
return nil, nil
}
23 changes: 18 additions & 5 deletions pkg/visor/visor.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,15 +675,28 @@ func (visor *Visor) Exec(command string) ([]byte, error) {
return cmd.CombinedOutput()
}

// Update checks if visor update is available.
// Update updates visor.
// It checks if visor update is available.
// If it is, the method downloads a new visor versions, starts it and kills the current process.
func (visor *Visor) Update() error {
if err := visor.updater.Update(); err != nil {
func (visor *Visor) Update() (bool, error) {
updated, err := visor.updater.Update()
if err != nil {
visor.logger.Errorf("Failed to update visor: %v", err)
return err
return false, err
}

return nil
return updated, nil
}

// UpdateAvailable checks if visor update is available.
func (visor *Visor) UpdateAvailable() (*updater.Version, error) {
version, err := visor.updater.UpdateAvailable()
if err != nil {
visor.logger.Errorf("Failed to check if visor update is available: %v", err)
return nil, err
}

return version, nil
}

func (visor *Visor) setAutoStart(appName string, autoStart bool) error {
Expand Down