mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-17 11:51:12 +02:00
Fixes #4420 No functional changes, just moving packages around. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
242 lines
5.8 KiB
Go
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
|
|
}
|
|
}
|
|
}
|