storj/private/version/checker/service.go
Clement Sam e27381f3af private/version: use minimum key in new sem version
Much evident on the storagenode dashboard, the minimum
version shown is a very old version and that is taken
from the deprecated part of version info from the
version control server, and we no longer update the
deprecated part on the server.

This change forces it to use the new sem version, and
checks for the old version if the server is probably
running an old version of the version control system.

Also fixes a bug where the suggested version returned
for all processes is taken from the storagenode part.

Issue: https://github.com/storj/storj/issues/5673

Change-Id: I57b7358c2826a6e25f441dfa9579a1aef50a7e89
2023-11-08 21:08:01 +00:00

185 lines
5.2 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package checker
import (
"context"
"fmt"
"sync"
"time"
"go.uber.org/zap"
"storj.io/common/sync2"
"storj.io/private/version"
)
// Config contains the necessary Information to check the Software Version.
type Config struct {
ClientConfig
CheckInterval time.Duration `help:"Interval to check the version" default:"0h15m0s"`
}
// Service contains the information and variables to ensure the Software is up to date.
//
// architecture: Service
type Service struct {
log *zap.Logger
config Config
client *Client
Info version.Info
service string
checked sync2.Fence
mu sync.Mutex
allowed bool
acceptedVersion version.SemVer
}
// NewService creates a Version Check Client with default configuration.
func NewService(log *zap.Logger, config Config, info version.Info, service string) (client *Service) {
return &Service{
log: log,
config: config,
client: New(config.ClientConfig),
Info: info,
service: service,
allowed: true,
}
}
// 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)
_, err = NewService(log, config, info, service).CheckVersion(ctx)
return err
}
// 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
}
service.mu.Lock()
defer service.mu.Unlock()
return service.acceptedVersion, service.allowed
}
// 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) (_ version.SemVer, allowed bool) {
var err error
defer mon.Task()(&ctx)(&err)
var minimum version.SemVer
defer func() {
service.mu.Lock()
service.allowed = allowed
if err == nil {
if minimum.Compare(service.acceptedVersion) >= 0 {
service.acceptedVersion = minimum
}
}
service.mu.Unlock()
service.checked.Release()
}()
process, err := service.client.Process(ctx, service.service)
if err != nil {
service.log.Error("failed to get process version info", zap.Error(err))
return service.acceptedVersion, true
}
suggestedVersion, err := process.Suggested.SemVer()
if err != nil {
return service.acceptedVersion, true
}
service.mu.Lock()
isVersionActual := suggestedVersion.Compare(service.acceptedVersion)
service.mu.Unlock()
if isVersionActual < 0 {
minimum = service.Info.Version
return service.acceptedVersion, true
}
if !service.Info.Release {
minimum = service.Info.Version
return suggestedVersion, true
}
minimum, err = process.Minimum.SemVer()
if err != nil {
return suggestedVersion, true
}
if minimum.IsZero() {
// if the minimum version is not set, we check if the old minimum version is set
// TODO: I'm not sure if we should remove this check and stop supporting the old format,
// but it seems like it's no longer needed, assuming there are no known community
// satellites (or SNOs personally) running an old version control server, which (I think)
// is very obviously 100% true currently.
minimumOld, err := service.client.OldMinimum(ctx, service.service)
if err != nil {
return suggestedVersion, true
}
minOld, err := version.NewSemVer(minimumOld.String())
if err != nil {
service.log.Error("failed to convert old sem version to new sem version", zap.Error(err))
return suggestedVersion, true
}
minimum = minOld
}
service.log.Debug("Allowed minimum version from control server.", zap.Stringer("Minimum Version", minimum.Version))
if service.Info.Version.Compare(minimum) >= 0 {
service.log.Debug("Running on allowed version.", zap.Stringer("Version", service.Info.Version.Version))
return suggestedVersion, true
}
service.log.Warn("version not allowed/outdated",
zap.Stringer("current version", service.Info.Version.Version),
zap.String("minimum allowed version", minimum.String()),
)
return suggestedVersion, false
}
// GetCursor returns storagenode rollout cursor value.
func (service *Service) GetCursor(ctx context.Context) (_ version.RolloutBytes, err error) {
allowedVersions, err := service.client.All(ctx)
if err != nil {
return version.RolloutBytes{}, err
}
return allowedVersions.Processes.Storagenode.Rollout.Cursor, nil
}
// SetAcceptedVersion changes accepted version to specific for tests.
func (service *Service) SetAcceptedVersion(version version.SemVer) {
service.mu.Lock()
defer service.mu.Unlock()
service.acceptedVersion = version
}
// Checked returns whether the version has been updated.
func (service *Service) Checked() bool {
return service.checked.Released()
}