storagenode: notifications on outdated software version

Change-Id: If19b075c78a7b2c441e11b783c3c09fed55060c7
This commit is contained in:
Qweder93 2020-02-21 19:41:54 +02:00 committed by Nikolai Siedov
parent 4d3db68283
commit 484ec7463a
15 changed files with 241 additions and 91 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View 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
})
}

View File

@ -120,6 +120,7 @@ func (client *Client) Process(ctx context.Context, processName string) (process
if !ok {
return version.Process{}, processNameErr
}
return process, nil
}

View File

@ -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.

View File

@ -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,
})
}

View File

@ -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,
})
}

View File

@ -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,
})
}

View File

@ -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,
})
}

View File

@ -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,

View File

@ -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,

View 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: "Its time to update your Nodes 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
})
}