Andrey Smirnov 753a82188f
refactor: move pkg/resources to machinery
Fixes #4420

No functional changes, just moving packages around.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
2021-11-15 19:50:35 +03:00

242 lines
5.8 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package time
import (
"context"
"fmt"
"sync"
stdtime "time"
"github.com/AlekSi/pointer"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"go.uber.org/zap"
v1alpha1runtime "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/pkg/ntp"
"github.com/talos-systems/talos/pkg/machinery/resources/config"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
"github.com/talos-systems/talos/pkg/machinery/resources/time"
)
// SyncController manages v1alpha1.TimeSync based on configuration and NTP sync process.
type SyncController struct {
V1Alpha1Mode v1alpha1runtime.Mode
NewNTPSyncer NewNTPSyncerFunc
bootTime stdtime.Time
}
// Name implements controller.Controller interface.
func (ctrl *SyncController) Name() string {
return "time.SyncController"
}
// Inputs implements controller.Controller interface.
func (ctrl *SyncController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: network.NamespaceName,
Type: network.TimeServerStatusType,
ID: pointer.ToString(network.TimeServerID),
Kind: controller.InputWeak,
},
{
Namespace: config.NamespaceName,
Type: config.MachineConfigType,
ID: pointer.ToString(config.V1Alpha1ID),
},
}
}
// Outputs implements controller.Controller interface.
func (ctrl *SyncController) Outputs() []controller.Output {
return []controller.Output{
{
Type: time.StatusType,
Kind: controller.OutputExclusive,
},
}
}
// NTPSyncer interface is implemented by ntp.Syncer, interface for mocking.
type NTPSyncer interface {
Run(ctx context.Context)
Synced() <-chan struct{}
EpochChange() <-chan struct{}
SetTimeServers([]string)
}
// NewNTPSyncerFunc function allows to replace ntp.Syncer with the mock.
type NewNTPSyncerFunc func(*zap.Logger, []string) NTPSyncer
// Run implements controller.Controller interface.
//
//nolint:gocyclo,cyclop
func (ctrl *SyncController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
if ctrl.bootTime.IsZero() {
ctrl.bootTime = stdtime.Now()
}
if ctrl.NewNTPSyncer == nil {
ctrl.NewNTPSyncer = func(logger *zap.Logger, timeServers []string) NTPSyncer {
return ntp.NewSyncer(logger, timeServers)
}
}
var (
syncCtx context.Context
syncCtxCancel context.CancelFunc
syncWg sync.WaitGroup
syncCh <-chan struct{}
epochCh <-chan struct{}
syncer NTPSyncer
timeSynced bool
epoch int
timeSyncTimeoutTimer *stdtime.Timer
timeSyncTimeoutCh <-chan stdtime.Time
)
defer func() {
if syncer != nil {
syncCtxCancel()
syncWg.Wait()
}
if timeSyncTimeoutTimer != nil {
timeSyncTimeoutTimer.Stop()
}
}()
for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
case <-syncCh:
syncCh = nil
timeSynced = true
case <-epochCh:
epoch++
case <-timeSyncTimeoutCh:
timeSynced = true
timeSyncTimeoutTimer = nil
}
timeServersStatus, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.TimeServerStatusType, network.TimeServerID, resource.VersionUndefined))
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting time server status: %w", err)
}
// time server list is not ready yet, wait for the next reconcile
continue
}
timeServers := timeServersStatus.(*network.TimeServerStatus).TypedSpec().NTPServers
cfg, err := r.Get(ctx, resource.NewMetadata(config.NamespaceName, config.MachineConfigType, config.V1Alpha1ID, resource.VersionUndefined))
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting config: %w", err)
}
}
var syncTimeout stdtime.Duration
syncDisabled := false
if ctrl.V1Alpha1Mode == v1alpha1runtime.ModeContainer {
syncDisabled = true
}
if cfg != nil && cfg.(*config.MachineConfig).Config().Machine().Time().Disabled() {
syncDisabled = true
}
if cfg != nil {
syncTimeout = cfg.(*config.MachineConfig).Config().Machine().Time().BootTimeout()
}
if !timeSynced {
sinceBoot := stdtime.Since(ctrl.bootTime)
switch {
case syncTimeout == 0:
// disable sync timeout
if timeSyncTimeoutTimer != nil {
timeSyncTimeoutTimer.Stop()
}
timeSyncTimeoutCh = nil
case sinceBoot > syncTimeout:
// over sync timeout already, so in sync
timeSynced = true
default:
// make sure timer fires in whatever time is left till the timeout
if timeSyncTimeoutTimer == nil || !timeSyncTimeoutTimer.Reset(syncTimeout-sinceBoot) {
timeSyncTimeoutTimer = stdtime.NewTimer(syncTimeout - sinceBoot)
timeSyncTimeoutCh = timeSyncTimeoutTimer.C
}
}
}
switch {
case syncDisabled && syncer != nil:
// stop syncing
syncCtxCancel()
syncWg.Wait()
syncer = nil
syncCh = nil
epochCh = nil
case !syncDisabled && syncer == nil:
// start syncing
syncer = ctrl.NewNTPSyncer(logger, timeServers)
syncCh = syncer.Synced()
epochCh = syncer.EpochChange()
timeSynced = false
syncCtx, syncCtxCancel = context.WithCancel(ctx) //nolint:govet
syncWg.Add(1)
go func() {
defer syncWg.Done()
syncer.Run(syncCtx)
}()
}
if syncer != nil {
syncer.SetTimeServers(timeServers)
}
if syncDisabled {
timeSynced = true
}
if err = r.Modify(ctx, time.NewStatus(), func(r resource.Resource) error {
r.(*time.Status).SetStatus(time.StatusSpec{
Epoch: epoch,
Synced: timeSynced,
SyncDisabled: syncDisabled,
})
return nil
}); err != nil {
return fmt.Errorf("error updating objects: %w", err) //nolint:govet
}
}
}