Skip to content

Commit

Permalink
storagenode: add run-mode flag to version checker chore
Browse files Browse the repository at this point in the history
This gives options to either disable the version checker in spc air-gapped
environments or set it to run once (especially for docker nodes that have
the updater binary already running periodically to check for new versions).

It is set to run periodically by default.

Issue: #6486
Change-Id: I40fa392309b9a36cd589e839298e2351d6814d9e
  • Loading branch information
profclems authored and Storj Robot committed Feb 7, 2024
1 parent f5871fb commit 1d84193
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 48 deletions.
5 changes: 4 additions & 1 deletion private/testplanet/storagenode.go
Expand Up @@ -43,6 +43,7 @@ import (
"storj.io/storj/storagenode/retain"
"storj.io/storj/storagenode/storagenodedb/storagenodedbtest"
"storj.io/storj/storagenode/trust"
"storj.io/storj/storagenode/version"
)

// StorageNode contains all the processes needed to run a full StorageNode setup.
Expand Down Expand Up @@ -202,7 +203,9 @@ func (planet *Planet) newStorageNode(ctx context.Context, prefix string, index,
Status: retain.Enabled,
Concurrency: 5,
},
Version: planet.NewVersionConfig(),
Version: version.Config{
Config: planet.NewVersionConfig(),
},
Bandwidth: bandwidth.Config{
Interval: defaultInterval,
},
Expand Down
27 changes: 17 additions & 10 deletions storagenode/peer.go
Expand Up @@ -64,7 +64,7 @@ import (
"storj.io/storj/storagenode/storagenodedb"
"storj.io/storj/storagenode/storageusage"
"storj.io/storj/storagenode/trust"
version2 "storj.io/storj/storagenode/version"
snVersion "storj.io/storj/storagenode/version"
)

var (
Expand Down Expand Up @@ -133,7 +133,7 @@ type Config struct {

Healthcheck healthcheck.Config

Version checker.Config
Version snVersion.Config

Bandwidth bandwidth.Config

Expand Down Expand Up @@ -216,7 +216,7 @@ type Peer struct {
Server *server.Server

Version struct {
Chore *version2.Chore
Chore *snVersion.Chore
Service *checker.Service
}

Expand Down Expand Up @@ -360,13 +360,20 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
)
}

peer.Version.Service = checker.NewService(log.Named("version"), config.Version, versionInfo, "Storagenode")
versionCheckInterval := 12 * time.Hour
peer.Version.Chore = version2.NewChore(peer.Log.Named("version:chore"), peer.Version.Service, peer.Notifications.Service, peer.Identity.ID, versionCheckInterval)
peer.Services.Add(lifecycle.Item{
Name: "version",
Run: peer.Version.Chore.Run,
})
if !config.Version.RunMode.Disabled() {
peer.Version.Service = checker.NewService(log.Named("version"), config.Version.Config, versionInfo, "Storagenode")
versionCheckInterval := 12 * time.Hour
peer.Version.Chore = snVersion.NewChore(peer.Log.Named("version:chore"), peer.Version.Service, peer.Notifications.Service, peer.Identity.ID, versionCheckInterval)
versionChore := lifecycle.Item{
Name: "version:chore",
Run: peer.Version.Chore.Run,
}

if config.Version.RunMode.Once() {
versionChore.Run = peer.Version.Chore.RunOnce
}
peer.Services.Add(versionChore)
}
}

{
Expand Down
79 changes: 42 additions & 37 deletions storagenode/version/chore.go
Expand Up @@ -37,14 +37,17 @@ type Chore struct {

// NewChore creates a Version Check Client with default configuration for storagenode.
func NewChore(log *zap.Logger, service *checker.Service, notifications *notifications.Service, nodeID storj.NodeID, checkInterval time.Duration) *Chore {
return &Chore{
chore := &Chore{
log: log,
service: service,
nodeID: nodeID,
notifications: notifications,
Loop: sync2.NewCycle(checkInterval),
nowFn: time.Now().UTC,
}

chore.version.init(service.Info.Version)
return chore
}

// Run logs the current version information and detects if software outdated, if so - sends notifications.
Expand All @@ -58,49 +61,51 @@ func (chore *Chore) Run(ctx context.Context) (err error) {
}
}

currentVer := chore.service.Info.Version
chore.version.init(currentVer)
return chore.Loop.Run(ctx, chore.RunOnce)
}

return chore.Loop.Run(ctx, func(ctx context.Context) error {
suggested, err := chore.checkVersion(ctx)
if err != nil {
return err
}
// RunOnce is like Run but only runs once.
func (chore *Chore) RunOnce(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err)

err = chore.checkRelevance(ctx, suggested, currentVer)
if err != nil {
return err
}
suggested, err := chore.checkVersion(ctx)
if err != nil {
return err
}

if !chore.version.IsOutdated {
return nil
}
err = chore.checkRelevance(ctx, suggested, chore.service.Info.Version)
if err != nil {
return err
}

var notification notifications.NewNotification
now := chore.nowFn()
switch {
case chore.version.FirstTimeSpotted.Add(time.Hour*336).Before(now) && chore.version.TimesNotified == notifications.TimesNotifiedSecond:
notification = NewVersionNotification(notifications.TimesNotifiedSecond, suggested, chore.nodeID)
chore.version.TimesNotified = notifications.TimesNotifiedLast

case chore.version.FirstTimeSpotted.Add(time.Hour*144).Before(now) && chore.version.TimesNotified == notifications.TimesNotifiedFirst:
notification = NewVersionNotification(notifications.TimesNotifiedFirst, suggested, chore.nodeID)
chore.version.TimesNotified = notifications.TimesNotifiedSecond

case chore.version.FirstTimeSpotted.Add(time.Hour*96).Before(now) && chore.version.TimesNotified == notifications.TimesNotifiedZero:
notification = NewVersionNotification(notifications.TimesNotifiedZero, suggested, chore.nodeID)
chore.version.TimesNotified = notifications.TimesNotifiedFirst
default:
return nil
}
if !chore.version.IsOutdated {
return nil
}

_, err = chore.notifications.Receive(ctx, notification)
if err != nil {
chore.log.Sugar().Errorf("Failed to receive notification", err.Error())
}
var notification notifications.NewNotification
now := chore.nowFn()
switch {
case chore.version.FirstTimeSpotted.Add(time.Hour*336).Before(now) && chore.version.TimesNotified == notifications.TimesNotifiedSecond:
notification = NewVersionNotification(notifications.TimesNotifiedSecond, suggested, chore.nodeID)
chore.version.TimesNotified = notifications.TimesNotifiedLast

case chore.version.FirstTimeSpotted.Add(time.Hour*144).Before(now) && chore.version.TimesNotified == notifications.TimesNotifiedFirst:
notification = NewVersionNotification(notifications.TimesNotifiedFirst, suggested, chore.nodeID)
chore.version.TimesNotified = notifications.TimesNotifiedSecond

case chore.version.FirstTimeSpotted.Add(time.Hour*96).Before(now) && chore.version.TimesNotified == notifications.TimesNotifiedZero:
notification = NewVersionNotification(notifications.TimesNotifiedZero, suggested, chore.nodeID)
chore.version.TimesNotified = notifications.TimesNotifiedFirst
default:
return nil
})
}

_, err = chore.notifications.Receive(ctx, notification)
if err != nil {
chore.log.Sugar().Errorf("Failed to receive notification", err.Error())
}

return nil
}

// checkVersion checks to make sure the node version is still relevant and returns the suggested
Expand Down
76 changes: 76 additions & 0 deletions storagenode/version/config.go
@@ -0,0 +1,76 @@
// Copyright (C) 2024 Storj Labs, Inc.
// See LICENSE for copying information.

package version

import (
"github.com/zeebo/errs"

"storj.io/storj/private/version/checker"
)

// Config is the config for the Storagenode Version Checker.
type Config struct {
checker.Config

RunMode Mode `help:"Define the run mode for the version checker. Options (once,periodic,disable)" default:"periodic"`
}

// Mode is the mode to run the version checker in.
type Mode string

const (
checkerModeOnce Mode = "once" // run the version checker once
checkerModePeriodic Mode = "periodic" // run the version checker periodically
checkerModeDisable Mode = "disable" // disable the version checker
)

// String implements pflag.Value.
func (m *Mode) String() string {
return string(*m)
}

// Set implements pflag.Value.
func (m *Mode) Set(s string) error {
if s == "" {
return errs.New("checker mode cannot be empty")
}

mode, err := ParseCheckerMode(s)
if err != nil {
return err
}

*m = mode
return nil
}

// Type implements pflag.Value.
func (m *Mode) Type() string {
return "run-mode"
}

// Disabled returns true if the checker is disabled.
func (m *Mode) Disabled() bool {
return *m == checkerModeDisable
}

// Periodic returns true if the checker is periodic.
func (m *Mode) Periodic() bool {
return *m == checkerModePeriodic
}

// Once returns true if the checker is once.
func (m *Mode) Once() bool {
return *m == checkerModeOnce
}

// ParseCheckerMode parses the string representation of the CheckerMode.
func ParseCheckerMode(s string) (Mode, error) {
switch Mode(s) {
case checkerModeOnce, checkerModePeriodic, checkerModeDisable:
return Mode(s), nil
default:
return "", errs.New("invalid checker run mode %q", s)
}
}

0 comments on commit 1d84193

Please sign in to comment.