storagenode: notifications on outdated software version
Change-Id: If19b075c78a7b2c441e11b783c3c09fed55060c7
This commit is contained in:
parent
4d3db68283
commit
484ec7463a
@ -58,7 +58,7 @@ func cmdAdminRun(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = peer.Version.CheckVersion(ctx)
|
||||
_, err = peer.Version.Service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ func cmdAPIRun(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = peer.Version.CheckVersion(ctx)
|
||||
_, err = peer.Version.Service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -244,8 +244,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
}
|
||||
|
||||
// okay, start doing stuff ====
|
||||
|
||||
err = peer.Version.CheckVersion(ctx)
|
||||
_, err = peer.Version.Service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func cmdRepairerRun(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = peer.Version.CheckVersion(ctx)
|
||||
_, err = peer.Version.Service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
// okay, start doing stuff ====
|
||||
|
||||
err = peer.Version.CheckVersion(ctx)
|
||||
_, err = peer.Version.Service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
41
private/version/checker/chore.go
Normal file
41
private/version/checker/chore.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"storj.io/common/sync2"
|
||||
)
|
||||
|
||||
// Chore contains the information and variables to ensure the Software is up to date.
|
||||
type Chore struct {
|
||||
service *Service
|
||||
|
||||
Loop *sync2.Cycle
|
||||
}
|
||||
|
||||
// NewChore creates a Version Check Client with default configuration.
|
||||
func NewChore(service *Service, checkInterval time.Duration) *Chore {
|
||||
return &Chore{
|
||||
service: service,
|
||||
Loop: sync2.NewCycle(checkInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// Run logs the current version information
|
||||
func (chore *Chore) Run(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
if !chore.service.Checked() {
|
||||
_, err := chore.service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return chore.Loop.Run(ctx, func(ctx context.Context) error {
|
||||
chore.service.checkVersion(ctx)
|
||||
return nil
|
||||
})
|
||||
}
|
@ -120,6 +120,7 @@ func (client *Client) Process(ctx context.Context, processName string) (process
|
||||
if !ok {
|
||||
return version.Process{}, processNameErr
|
||||
}
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,9 @@ type Service struct {
|
||||
log *zap.Logger
|
||||
config Config
|
||||
client *Client
|
||||
info version.Info
|
||||
Info version.Info
|
||||
service string
|
||||
|
||||
Loop *sync2.Cycle
|
||||
|
||||
checked sync2.Fence
|
||||
mu sync.Mutex
|
||||
allowed bool
|
||||
@ -47,101 +45,100 @@ func NewService(log *zap.Logger, config Config, info version.Info, service strin
|
||||
log: log,
|
||||
config: config,
|
||||
client: New(config.ClientConfig),
|
||||
info: info,
|
||||
Info: info,
|
||||
service: service,
|
||||
Loop: sync2.NewCycle(config.CheckInterval),
|
||||
allowed: true,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckVersion checks to make sure the version is still okay, returning an error if not
|
||||
func (srv *Service) CheckVersion(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
if !srv.checkVersion(ctx) {
|
||||
return fmt.Errorf("outdated software version (%v), please update", srv.info.Version.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckProcessVersion is not meant to be used for peers but is meant to be
|
||||
// used for other utilities
|
||||
func CheckProcessVersion(ctx context.Context, log *zap.Logger, config Config, info version.Info, service string) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
return NewService(log, config, info, service).CheckVersion(ctx)
|
||||
_, err = NewService(log, config, info, service).CheckVersion(ctx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Run logs the current version information
|
||||
func (srv *Service) Run(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
if !srv.checked.Released() {
|
||||
err := srv.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return srv.Loop.Run(ctx, func(ctx context.Context) error {
|
||||
srv.checkVersion(ctx)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// IsAllowed returns whether if the Service is allowed to operate or not
|
||||
func (srv *Service) IsAllowed(ctx context.Context) (version.SemVer, bool) {
|
||||
if !srv.checked.Wait(ctx) {
|
||||
// IsAllowed returns whether if the Service is allowed to operate or not.
|
||||
func (service *Service) IsAllowed(ctx context.Context) (version.SemVer, bool) {
|
||||
if !service.checked.Wait(ctx) {
|
||||
return version.SemVer{}, false
|
||||
}
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
return srv.acceptedVersion, srv.allowed
|
||||
service.mu.Lock()
|
||||
defer service.mu.Unlock()
|
||||
return service.acceptedVersion, service.allowed
|
||||
}
|
||||
|
||||
// checkVersion checks if the client is running latest/allowed code
|
||||
func (srv *Service) checkVersion(ctx context.Context) (allowed bool) {
|
||||
// CheckVersion checks to make sure the version is still relevant and returns suggested version, returning an error if not.
|
||||
func (service *Service) CheckVersion(ctx context.Context) (latest version.SemVer, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
latest, allowed := service.checkVersion(ctx)
|
||||
if !allowed {
|
||||
return latest, fmt.Errorf("outdated software version (%s), please update", service.Info.Version.String())
|
||||
}
|
||||
return latest, nil
|
||||
}
|
||||
|
||||
// checkVersion checks if the client is running latest/allowed version.
|
||||
func (service *Service) checkVersion(ctx context.Context) (latestVersion version.SemVer, allowed bool) {
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var minimum version.SemVer
|
||||
|
||||
defer func() {
|
||||
srv.mu.Lock()
|
||||
srv.allowed = allowed
|
||||
service.mu.Lock()
|
||||
service.allowed = allowed
|
||||
if err == nil {
|
||||
srv.acceptedVersion = minimum
|
||||
service.acceptedVersion = minimum
|
||||
}
|
||||
srv.mu.Unlock()
|
||||
srv.checked.Release()
|
||||
service.mu.Unlock()
|
||||
service.checked.Release()
|
||||
}()
|
||||
|
||||
if !srv.info.Release {
|
||||
minimum = srv.info.Version
|
||||
return true
|
||||
allowedVersions, err := service.client.All(ctx)
|
||||
if err != nil {
|
||||
return service.acceptedVersion, true
|
||||
}
|
||||
suggestedVersion, err := allowedVersions.Processes.Storagenode.Suggested.SemVer()
|
||||
if err != nil {
|
||||
return service.acceptedVersion, true
|
||||
}
|
||||
|
||||
minimumOld, err := srv.client.OldMinimum(ctx, srv.service)
|
||||
if !service.Info.Release {
|
||||
minimum = service.Info.Version
|
||||
return suggestedVersion, true
|
||||
}
|
||||
|
||||
minimumOld, err := service.client.OldMinimum(ctx, service.service)
|
||||
if err != nil {
|
||||
// Log about the error, but dont crash the service and allow further operation
|
||||
srv.log.Sugar().Errorf("Failed to do periodic version check: %s", err.Error())
|
||||
return true
|
||||
// Log about the error, but dont crash the Service and allow further operation
|
||||
service.log.Sugar().Errorf("Failed to do periodic version check: %s", err.Error())
|
||||
return suggestedVersion, true
|
||||
}
|
||||
|
||||
minimum, err = version.NewSemVer(minimumOld.String())
|
||||
if err != nil {
|
||||
srv.log.Sugar().Errorf("failed to convert old sem version to sem version")
|
||||
return true
|
||||
service.log.Sugar().Errorf("failed to convert old sem version to sem version")
|
||||
return suggestedVersion, true
|
||||
}
|
||||
|
||||
srv.log.Sugar().Debugf("allowed minimum version from control server is: %s", minimum.String())
|
||||
service.log.Sugar().Debugf("allowed minimum version from control server is: %s", minimum.String())
|
||||
|
||||
if minimum.String() == "" {
|
||||
srv.log.Sugar().Errorf("no version from control server, accepting to run")
|
||||
return true
|
||||
if isAcceptedVersion(service.Info.Version, minimumOld) {
|
||||
service.log.Sugar().Infof("running on version %s", service.Info.Version.String())
|
||||
return suggestedVersion, true
|
||||
}
|
||||
if isAcceptedVersion(srv.info.Version, minimumOld) {
|
||||
srv.log.Sugar().Infof("running on version %s", srv.info.Version.String())
|
||||
return true
|
||||
}
|
||||
srv.log.Sugar().Errorf("running on not allowed/outdated version %s", srv.info.Version.String())
|
||||
return false
|
||||
|
||||
service.log.Sugar().Errorf("running on not allowed/outdated version %s", service.Info.Version.String())
|
||||
|
||||
return suggestedVersion, false
|
||||
}
|
||||
|
||||
// Checked returns whether the version has been updated.
|
||||
func (service *Service) Checked() bool {
|
||||
return service.checked.Released()
|
||||
}
|
||||
|
||||
// DebugHandler implements version info endpoint.
|
||||
|
@ -41,7 +41,10 @@ type Admin struct {
|
||||
Server *debug.Server
|
||||
}
|
||||
|
||||
Version *checker.Service
|
||||
Version struct {
|
||||
Chore *checker.Chore
|
||||
Service *checker.Service
|
||||
}
|
||||
|
||||
Admin struct {
|
||||
Listener net.Listener
|
||||
@ -88,11 +91,12 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Log.Sugar().Debugf("Binary Version: %s with CommitHash %s, built at %s as Release %v",
|
||||
versionInfo.Version.String(), versionInfo.CommitHash, versionInfo.Timestamp.String(), versionInfo.Release)
|
||||
}
|
||||
peer.Version = checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Service = checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Chore = checker.NewChore(peer.Version.Service, config.Version.CheckInterval)
|
||||
|
||||
peer.Services.Add(lifecycle.Item{
|
||||
Name: "version",
|
||||
Run: peer.Version.Run,
|
||||
Run: peer.Version.Chore.Run,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -65,9 +65,13 @@ type API struct {
|
||||
Servers *lifecycle.Group
|
||||
Services *lifecycle.Group
|
||||
|
||||
Dialer rpc.Dialer
|
||||
Server *server.Server
|
||||
Version *checker.Service
|
||||
Dialer rpc.Dialer
|
||||
Server *server.Server
|
||||
|
||||
Version struct {
|
||||
Chore *checker.Chore
|
||||
Service *checker.Service
|
||||
}
|
||||
|
||||
Debug struct {
|
||||
Listener net.Listener
|
||||
@ -195,11 +199,13 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Log.Sugar().Debugf("Binary Version: %s with CommitHash %s, built at %s as Release %v",
|
||||
versionInfo.Version.String(), versionInfo.CommitHash, versionInfo.Timestamp.String(), versionInfo.Release)
|
||||
}
|
||||
peer.Version = checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
|
||||
peer.Version.Service = checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Chore = checker.NewChore(peer.Version.Service, config.Version.CheckInterval)
|
||||
|
||||
peer.Services.Add(lifecycle.Item{
|
||||
Name: "version",
|
||||
Run: peer.Version.Run,
|
||||
Run: peer.Version.Chore.Run,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,10 @@ type Core struct {
|
||||
|
||||
Dialer rpc.Dialer
|
||||
|
||||
Version *version_checker.Service
|
||||
Version struct {
|
||||
Chore *version_checker.Chore
|
||||
Service *version_checker.Service
|
||||
}
|
||||
|
||||
Debug struct {
|
||||
Listener net.Listener
|
||||
@ -178,11 +181,12 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Log.Sugar().Debugf("Binary Version: %s with CommitHash %s, built at %s as Release %v",
|
||||
versionInfo.Version.String(), versionInfo.CommitHash, versionInfo.Timestamp.String(), versionInfo.Release)
|
||||
}
|
||||
peer.Version = version_checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Service = version_checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Chore = version_checker.NewChore(peer.Version.Service, config.Version.CheckInterval)
|
||||
|
||||
peer.Services.Add(lifecycle.Item{
|
||||
Name: "version",
|
||||
Run: peer.Version.Run,
|
||||
Run: peer.Version.Chore.Run,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,12 @@ type Repairer struct {
|
||||
Servers *lifecycle.Group
|
||||
Services *lifecycle.Group
|
||||
|
||||
Dialer rpc.Dialer
|
||||
Version *version_checker.Service
|
||||
Dialer rpc.Dialer
|
||||
|
||||
Version struct {
|
||||
Chore *version_checker.Chore
|
||||
Service *version_checker.Service
|
||||
}
|
||||
|
||||
Debug struct {
|
||||
Listener net.Listener
|
||||
@ -100,11 +104,12 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity,
|
||||
peer.Log.Sugar().Debugf("Binary Version: %s with CommitHash %s, built at %s as Release %v",
|
||||
versionInfo.Version.String(), versionInfo.CommitHash, versionInfo.Timestamp.String(), versionInfo.Release)
|
||||
}
|
||||
peer.Version = version_checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Service = version_checker.NewService(log.Named("version"), config.Version, versionInfo, "Satellite")
|
||||
peer.Version.Chore = version_checker.NewChore(peer.Version.Service, config.Version.CheckInterval)
|
||||
|
||||
peer.Services.Add(lifecycle.Item{
|
||||
Name: "version",
|
||||
Run: peer.Version.Run,
|
||||
Run: peer.Version.Chore.Run,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,7 @@ func NewService(log *zap.Logger, bandwidth bandwidth.DB, pieceStore *pieces.Stor
|
||||
if contact == nil {
|
||||
return nil, errs.New("contact service can't be nil")
|
||||
}
|
||||
|
||||
return &Service{
|
||||
log: log,
|
||||
trust: trust,
|
||||
|
@ -50,6 +50,7 @@ import (
|
||||
"storj.io/storj/storagenode/satellites"
|
||||
"storj.io/storj/storagenode/storageusage"
|
||||
"storj.io/storj/storagenode/trust"
|
||||
version2 "storj.io/storj/storagenode/version"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -164,7 +165,10 @@ type Peer struct {
|
||||
|
||||
Server *server.Server
|
||||
|
||||
Version *checker.Service
|
||||
Version struct {
|
||||
Chore *version2.Chore
|
||||
Service *checker.Service
|
||||
}
|
||||
|
||||
Debug struct {
|
||||
Listener net.Listener
|
||||
@ -236,6 +240,10 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
Services: lifecycle.NewGroup(log.Named("services")),
|
||||
}
|
||||
|
||||
{ // setup notification service.
|
||||
peer.Notifications.Service = notifications.NewService(peer.Log, peer.DB.Notifications())
|
||||
}
|
||||
|
||||
{ // setup debug
|
||||
var err error
|
||||
if config.Debug.Address != "" {
|
||||
@ -263,11 +271,13 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
peer.Log.Sugar().Debugf("Binary Version: %s with CommitHash %s, built at %s as Release %v",
|
||||
versionInfo.Version.String(), versionInfo.CommitHash, versionInfo.Timestamp.String(), versionInfo.Release)
|
||||
}
|
||||
peer.Version = checker.NewService(log.Named("version"), config.Version, versionInfo, "Storagenode")
|
||||
|
||||
peer.Version.Service = checker.NewService(log.Named("version"), config.Version, versionInfo, "Storagenode")
|
||||
versionCheckInterval := 24 * time.Hour
|
||||
peer.Version.Chore = version2.NewChore(peer.Version.Service, peer.Notifications.Service, peer.Identity.ID, versionCheckInterval)
|
||||
peer.Services.Add(lifecycle.Item{
|
||||
Name: "version",
|
||||
Run: peer.Version.Run,
|
||||
Run: peer.Version.Chore.Run,
|
||||
})
|
||||
}
|
||||
|
||||
@ -313,10 +323,6 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
peer.Preflight.LocalTime = preflight.NewLocalTime(peer.Log.Named("preflight:localtime"), config.Preflight, peer.Storage2.Trust, peer.Dialer)
|
||||
}
|
||||
|
||||
{ // setup notification service.
|
||||
peer.Notifications.Service = notifications.NewService(peer.Log, peer.DB.Notifications())
|
||||
}
|
||||
|
||||
{ // setup contact service
|
||||
c := config.Contact
|
||||
if c.ExternalAddress == "" {
|
||||
@ -513,7 +519,7 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
peer.Log.Named("console:service"),
|
||||
peer.DB.Bandwidth(),
|
||||
peer.Storage2.Store,
|
||||
peer.Version,
|
||||
peer.Version.Service,
|
||||
config.Storage.AllocatedBandwidth,
|
||||
config.Storage.AllocatedDiskSpace,
|
||||
config.Operator.Wallet,
|
||||
|
86
storagenode/version/chore.go
Normal file
86
storagenode/version/chore.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/sync2"
|
||||
"storj.io/storj/pkg/storj"
|
||||
"storj.io/storj/private/version/checker"
|
||||
"storj.io/storj/storagenode/notifications"
|
||||
)
|
||||
|
||||
var (
|
||||
mon = monkit.Package()
|
||||
)
|
||||
|
||||
// Chore contains the information and variables to ensure the Software is up to date for storagenode.
|
||||
type Chore struct {
|
||||
log *zap.Logger
|
||||
service *checker.Service
|
||||
|
||||
Loop *sync2.Cycle
|
||||
nodeID storj.NodeID
|
||||
notifications *notifications.Service
|
||||
}
|
||||
|
||||
// NewChore creates a Version Check Client with default configuration for storagenode.
|
||||
func NewChore(service *checker.Service, notifications *notifications.Service, nodeID storj.NodeID, checkInterval time.Duration) *Chore {
|
||||
return &Chore{
|
||||
service: service,
|
||||
nodeID: nodeID,
|
||||
notifications: notifications,
|
||||
Loop: sync2.NewCycle(checkInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// Run logs the current version information
|
||||
func (chore *Chore) Run(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if !chore.service.Checked() {
|
||||
_, err = chore.service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return chore.Loop.Run(ctx, func(ctx context.Context) error {
|
||||
suggested, err := chore.service.CheckVersion(ctx)
|
||||
if err != nil {
|
||||
notification := notifications.NewNotification{
|
||||
SenderID: chore.nodeID,
|
||||
Type: notifications.TypeCustom,
|
||||
Title: "It’s time to update your Node’s software, you are running outdated version " + chore.service.Info.Version.String(),
|
||||
Message: "Failure to update your software soon will impact your reputation and payout amount because your Node could potentially be disqualified shortly",
|
||||
}
|
||||
|
||||
_, err = chore.notifications.Receive(ctx, notification)
|
||||
if err != nil {
|
||||
chore.log.Sugar().Errorf("Failed to insert notification", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if chore.service.Info.Version.Compare(suggested) < 0 {
|
||||
notification := notifications.NewNotification{
|
||||
SenderID: chore.nodeID,
|
||||
Type: notifications.TypeCustom,
|
||||
Title: "Update your Node to Version " + suggested.String(),
|
||||
Message: "It's time to update your Node's software, you are running outdated version " + chore.service.Info.Version.String(),
|
||||
}
|
||||
|
||||
_, err = chore.notifications.Receive(ctx, notification)
|
||||
if err != nil {
|
||||
chore.log.Sugar().Errorf("Failed to insert notification", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user