Help us shape the future of Go! Take the 2026 Go Developer Survey

configchange

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

pkg/controller/configchange

Configuration validation orchestrator and reinitialization signaller.

Overview

ConfigChangeHandler bridges configuration parsing, validation, and controller reinitialization. It does not watch any Kubernetes resource — that's the job of the CRD watcher in pkg/controller. Instead it consumes events:

  1. Validation orchestration — subscribes to ConfigParsedEvent, fans a ConfigValidationRequest out to all registered validators (basic, template, jsonpath, validationtests) using the bus's scatter-gather (bus.Request), aggregates the ConfigValidationResponse events, and publishes either ConfigValidatedEvent or ConfigInvalidEvent. When validators report that they couldn't judge the config (ConfigValidationResponse.Inconclusive, e.g. schemas transiently unavailable) and none rejected it, it publishes ConfigValidationInconclusiveEvent, keeps the running config, and validates the same candidate again with backoff (1 s doubling to 30 s) until a verdict arrives or a newer config supersedes it. The scatter-gather can take tens of seconds, so it runs off the event loop — single-flight, latest-wins for parsed configs arriving mid-validation — keeping side events responsive throughout.
  2. Reinitialization signal — keeps the running iteration's active snapshot separate from its latest accepted candidate and hands one authoritative snapshot to the controller. Config, credential, and effective-resolution reasons are tracked independently, so superseding a config candidate can't discard a credential or schema reload. Retiring an accepted candidate also replays the active snapshot to state consumers without producing another validation verdict or reload.

This package also contains StatusUpdater, which writes validation results back onto the HAProxyTemplateConfig CRD's status subresource. It runs on the leader only and re-asserts the leader's verdict when another controller (an old version during a rolling upgrade, or a replica's startup load gate) overwrites it for the same or an older generation.

Leadership handover replays the real validation result, including its source generations. A synthetic startup event only seeds an empty replay cache; it cannot replace a validation result or turn an active-state restoration into a new verdict.

Quick Start

import "github.com/haproxy-haptic/HAPTIC/pkg/controller/configchange"

configChangeCh := make(chan *configchange.ReloadRequest, 1)

handler := configchange.NewConfigChangeHandler(
    bus,
    logger,
    configChangeCh,
    []string{"basic", "template", "jsonpath"}, // validator names that must respond
    0,                                         // 0 → DefaultReinitDebounceInterval (2s)
)
go handler.Start(ctx)

// Elsewhere: react to the reinit signal
for request := range configChangeCh {
    // controller restarts its iteration with request.Snapshot
}

The next iteration consumes the snapshot rather than refetching a newer, unvalidated CR. A newer parsed config retracts an accepted candidate until that newer generation passes, while independent credential and schema reloads continue from the active snapshot. Schema changes re-resolve the selected raw config and run the complete config-validation contract before activation. Exact startup-version echoes are ignored; newer changes observed during startup are replayed latest-wins by EnableReinitialization.

Events

  • Subscribes: ConfigParsedEvent, ConfigValidatedEvent, BecameLeaderEvent, CredentialsUpdatedEvent
  • Publishes: ConfigValidationRequest (scatter), ConfigValidatedEvent, ConfigInvalidEvent, ConfigValidationInconclusiveEvent
  • Receives: ConfigValidationResponse (gather — collected via bus.Request, not a registered responder)

License

Apache-2.0 — see root LICENSE.

Documentation

Index

Constants

View Source
const (
	// ComponentName is the unique identifier for this component.
	ComponentName = "configchange-handler"

	// EventBufferSize is the size of the event subscription buffer.
	// Moderate-volume component handling config and validation events.
	EventBufferSize = busevents.StandardSubscriberBuffer
)
View Source
const (
	// StatusUpdaterComponentName is the unique identifier for this component.
	StatusUpdaterComponentName = "status-updater"

	// StatusUpdaterEventBufferSize is the size of the event subscription buffer.
	StatusUpdaterEventBufferSize = busevents.StandardSubscriberBuffer
)
View Source
const DefaultReinitDebounceInterval = 2 * time.Second

DefaultReinitDebounceInterval is the default time to wait after the last config change before signaling controller reinitialization. This allows rapid CRD updates to be coalesced, ensuring templates are fully rendered before reinitialization starts. Stays lenient where the per-watcher default (types.DefaultDebounceInterval) is near zero: reinit tears down and rebuilds every informer, so coalescing a burst of operator-initiated CRD edits is worth the added seconds.

Variables

This section is empty.

Functions

func ReportConfigLoadFailure

func ReportConfigLoadFailure(
	ctx context.Context,
	crdClient versioned.Interface,
	ref events.ConfigSourceRef,
	failures []string,
	logger *slog.Logger,
)

ReportConfigLoadFailure reports rejection before StatusUpdater starts. Reporting errors never mask the load failure; the pod stays NotReady and retries.

Types

type ConfigChangeHandler

type ConfigChangeHandler struct {
	// contains filtered or unexported fields
}

ConfigChangeHandler coordinates configuration validation and detects config changes.

This component has two main responsibilities:

  1. Validation Coordination: Subscribes to ConfigParsedEvent, sends ConfigValidationRequest, collects responses from validators using scatter-gather pattern, and publishes ConfigValidatedEvent or ConfigInvalidEvent. When validators cannot judge the config, it publishes ConfigValidationInconclusiveEvent and retries.

  2. Change Detection: Subscribes to ConfigValidatedEvent and signals the controller to reinitialize via the configChangeCh channel with debouncing to coalesce rapid changes.

Debouncing behavior: When multiple CRD config changes arrive in rapid succession, the handler debounces the reinitialization signal. This ensures all pending renders complete before reinitialization starts, preventing the race condition where reinitialization cancels in-progress renders.

Architecture: This component bridges the gap between configuration parsing and validation, and between validation and controller reinitialization. It uses the scatter-gather pattern from the event bus for coordinating validation across multiple validators.

func NewConfigChangeHandler

func NewConfigChangeHandler(
	eventBus *busevents.EventBus,
	logger *slog.Logger,
	configChangeCh chan *ReloadRequest,
	validators []string,
	debounceInterval time.Duration,
) *ConfigChangeHandler

NewConfigChangeHandler creates a new ConfigChangeHandler.

Parameters:

  • eventBus: The EventBus to subscribe to and publish on
  • logger: Structured logger for diagnostics
  • configChangeCh: Channel to signal controller reinitialization with validated config
  • validators: List of expected validator names (e.g., ["basic", "template", "jsonpath"])
  • debounceInterval: Time to wait after last config change before triggering reinitialization. Use 0 for default (500ms).

Returns:

  • *ConfigChangeHandler ready to start

func (*ConfigChangeHandler) EnableReinitialization

func (h *ConfigChangeHandler) EnableReinitialization()

EnableReinitialization enables the reinitialization signaling mechanism.

This must be called after controller startup is complete to allow config changes to trigger reinitialization. During startup, ConfigValidatedEvents occur that should NOT trigger reinitialization. Calling this method signals that the startup phase is complete and future config changes should trigger reinitialization.

Note: CRDWatcher uses generation-based filtering, so status-only updates (which don't increment generation) never trigger ConfigValidatedEvents in the first place.

func (*ConfigChangeHandler) RequestEffectiveReload

func (h *ConfigChangeHandler) RequestEffectiveReload()

RequestEffectiveReload asks the handler to restart from its accepted raw config after resolving API versions again.

func (*ConfigChangeHandler) SetEffectiveResolver

func (h *ConfigChangeHandler) SetEffectiveResolver(resolve func(*coreconfig.Config) (*ResolvedConfig, error))

SetEffectiveResolver installs the effective-config transformation applied to every parsed config before validation (see the field doc). Like SetInitialConfigVersion, this must be called after construction and before the CRD watcher starts delivering events.

func (*ConfigChangeHandler) SetInitialConfigVersion

func (h *ConfigChangeHandler) SetInitialConfigVersion(version string)

SetInitialConfigVersion sets the initial config version to prevent reinitialization on bootstrap ConfigValidatedEvent.

This must be called after fetching the initial config but before CRDWatcher starts. When CRDWatcher's informer triggers onAdd for the existing CRD, it publishes a ConfigValidatedEvent with the same version. Without this tracking, that event would trigger reinitialization, creating an infinite loop.

Parameters:

  • version: The resourceVersion from the initial CRD fetch

func (*ConfigChangeHandler) SetInitialSnapshot

func (h *ConfigChangeHandler) SetInitialSnapshot(snapshot *ValidatedSnapshot)

SetInitialSnapshot records the configuration and credentials owned by the running iteration. It must run before reinitialization is enabled.

func (*ConfigChangeHandler) Start

func (h *ConfigChangeHandler) Start(ctx context.Context) error

Start begins processing events from the EventBus.

This method blocks until the context is canceled. The component is already subscribed to the EventBus (subscription happens in constructor). Returns nil on graceful shutdown.

Example:

go handler.Start(ctx)

type ReloadReason

type ReloadReason uint8

ReloadReason identifies independent desired-state changes carried by a reload.

const (
	ReloadReasonConfig ReloadReason = 1 << iota
	ReloadReasonCredentials
	ReloadReasonEffectiveConfig
)

func (ReloadReason) Has

func (r ReloadReason) Has(reason ReloadReason) bool

Has reports whether the reason set contains reason.

type ReloadRequest

type ReloadRequest struct {
	Snapshot *ValidatedSnapshot
	Reasons  ReloadReason
}

ReloadRequest carries the authoritative state for the next iteration. An effective-config reason requires re-resolving Snapshot.RawConfig; otherwise Snapshot.Config is already validated and must be used verbatim.

type ResolvedConfig

type ResolvedConfig struct {
	Config     *coreconfig.Config
	Resolution *coreconfig.Resolution
}

ResolvedConfig is the effective configuration and the discovery outcome that produced it. Validators and the next controller iteration consume this pair together.

type StatusUpdater

type StatusUpdater struct {
	*component.Base
	*component.ReadySignal
	// contains filtered or unexported fields
}

StatusUpdater updates HAProxyTemplateConfig status based on validation results.

This component subscribes to ConfigValidatedEvent, ConfigInvalidEvent, and ValidationFailedEvent, updating the HAProxyTemplateConfig CRD status to reflect validation state. Users can then see validation errors via `kubectl describe haproxytemplateconfig`.

Architecture: This is an event adapter that bridges validation events to Kubernetes API updates. It uses the generated typed client for HAProxyTemplateConfig status updates.

The component handles two types of validation: - Config validation (Stage 1): Template syntax, JSONPath expressions, etc. - HAProxy validation (Stage 4): Rendered config syntax check with haproxy -c.

func NewStatusUpdater

func NewStatusUpdater(
	crdClient versioned.Interface,
	kubeClient kubernetes.Interface,
	eventBus *busevents.EventBus,
	logger *slog.Logger,
) *StatusUpdater

NewStatusUpdater creates a new StatusUpdater.

Parameters:

  • crdClient: typed client for HAProxyTemplateConfig CRD status updates
  • kubeClient: core client used to emit Kubernetes Events on the CRD
  • eventBus: EventBus to subscribe to validation events
  • logger: Structured logger for diagnostics

Returns:

  • *StatusUpdater ready to start

func (*StatusUpdater) HandleEvent

func (u *StatusUpdater) HandleEvent(event busevents.Event)

HandleEvent implements component.EventHandler, routing each validation event to the matching status-update handler using the loop context captured by Start.

func (*StatusUpdater) Start

func (u *StatusUpdater) Start(ctx context.Context) error

Start captures the loop context for handlers and runs the embedded component.Base event loop until the context is cancelled or Stop is called. Returns nil on graceful shutdown.

type ValidatedSnapshot

type ValidatedSnapshot struct {
	RawConfig          *coreconfig.Config
	Config             *coreconfig.Config
	Resolution         *coreconfig.Resolution
	TemplateConfig     any
	ConfigVersion      string
	Credentials        *coreconfig.Credentials
	CredentialsVersion string
	Sources            []events.ConfigSourceRef
}

ValidatedSnapshot is the complete input accepted for one controller iteration. Config is effective; RawConfig retains candidate API versions for later discovery re-resolution.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL