diff --git a/internal/app/machined/pkg/controllers/network/address_merge.go b/internal/app/machined/pkg/controllers/network/address_merge.go index fea039806c..6c2f7dad7a 100644 --- a/internal/app/machined/pkg/controllers/network/address_merge.go +++ b/internal/app/machined/pkg/controllers/network/address_merge.go @@ -2,138 +2,41 @@ // 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 network provides controllers which manage network resources. -// -//nolint:dupl package network import ( - "context" - "fmt" - "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// AddressMergeController merges network.AddressSpec in network.ConfigNamespace and produces final network.AddressSpec in network.Namespace. -type AddressMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *AddressMergeController) Name() string { - return "network.AddressMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *AddressMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.AddressSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.AddressSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *AddressMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.AddressSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewAddressMergeController initializes a AddressMergeController. // -//nolint:gocyclo -func (ctrl *AddressMergeController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.AddressSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network addresses: %w", err) - } - - // address is allowed as long as it's not duplicate, for duplicate higher layer takes precedence - addresses := map[string]*network.AddressSpec{} - - for _, res := range list.Items { - address := res.(*network.AddressSpec) //nolint:forcetypeassert - id := network.AddressID(address.TypedSpec().LinkName, address.TypedSpec().Address) - - existing, ok := addresses[id] - if ok && existing.TypedSpec().ConfigLayer > address.TypedSpec().ConfigLayer { - // skip this address, as existing one is higher layer - continue - } - - addresses[id] = address - } - - conflictsDetected := 0 - - for id, address := range addresses { - if err = safe.WriterModify(ctx, r, network.NewAddressSpec(network.NamespaceName, id), func(addr *network.AddressSpec) error { - *addr.TypedSpec() = *address.TypedSpec() - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // phase conflict, resource is being torn down, skip updating it and trigger reconcile - // later by failing the - conflictsDetected++ - - delete(addresses, id) - } else { - return fmt.Errorf("error updating resource: %w", err) - } - } - } - - // list addresses for cleanup - list, err = r.List(ctx, resource.NewMetadata(network.NamespaceName, network.AddressSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing resources: %w", err) - } - - for _, res := range list.Items { - if _, ok := addresses[res.Metadata().ID()]; !ok { - var okToDestroy bool - - okToDestroy, err = r.Teardown(ctx, res.Metadata()) - if err != nil { - return fmt.Errorf("error cleaning up addresses: %w", err) +// AddressMergeController merges network.AddressSpec in network.ConfigNamespace and produces final network.AddressSpec in network.Namespace. +func NewAddressMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.AddressSpec]) map[resource.ID]*network.AddressSpecSpec { + // address is allowed as long as it's not duplicate, for duplicate higher layer takes precedence + addresses := map[resource.ID]*network.AddressSpecSpec{} + + for address := range list.All() { + id := network.AddressID(address.TypedSpec().LinkName, address.TypedSpec().Address) + + existing, ok := addresses[id] + if ok && existing.ConfigLayer > address.TypedSpec().ConfigLayer { + // skip this address, as existing one is higher layer + continue } - if okToDestroy { - if err = r.Destroy(ctx, res.Metadata()); err != nil { - return fmt.Errorf("error cleaning up addresses: %w", err) - } - } + addresses[id] = address.TypedSpec() } - } - if conflictsDetected > 0 { - return fmt.Errorf("%d conflict(s) detected", conflictsDetected) - } - - r.ResetRestartBackoff() - } + return addresses + }, + ) } diff --git a/internal/app/machined/pkg/controllers/network/address_merge_test.go b/internal/app/machined/pkg/controllers/network/address_merge_test.go index 0a49e4b595..9064cd220f 100644 --- a/internal/app/machined/pkg/controllers/network/address_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/address_merge_test.go @@ -52,7 +52,7 @@ func (suite *AddressMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.AddressMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewAddressMergeController())) suite.startRuntime() } @@ -152,7 +152,7 @@ func (suite *AddressMergeSuite) TestMerge() { suite.assertAddresses( []string{ "lo/127.0.0.1/8", - "eth0/10.0.0.35/32", + "eth0/10.0.0.1/8", }, func(*network.AddressSpec, *assert.Assertions) {}, ) suite.Assert().NoError( diff --git a/internal/app/machined/pkg/controllers/network/generic_merge.go b/internal/app/machined/pkg/controllers/network/generic_merge.go new file mode 100644 index 0000000000..39ba17d2dc --- /dev/null +++ b/internal/app/machined/pkg/controllers/network/generic_merge.go @@ -0,0 +1,142 @@ +// 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 network + +import ( + "context" + "fmt" + "strings" + + "github.com/cosi-project/runtime/pkg/controller" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/typed" + "github.com/cosi-project/runtime/pkg/safe" + "go.uber.org/zap" +) + +type genericMergeFunc[T typed.DeepCopyable[T], E typed.Extension, R typed.Resource[T, E]] func(logger *zap.Logger, in safe.List[*R]) map[resource.ID]*T + +// GenericMergeController initializes a generic merge controller for network resources. +func GenericMergeController[T typed.DeepCopyable[T], E typed.Extension](namespaceIn, namespaceOut resource.Namespace, mergeFunc genericMergeFunc[T, E, typed.Resource[T, E]]) controller.Controller { + var zeroE E + + controllerName := strings.ReplaceAll(zeroE.ResourceDefinition().Type, "Spec", "MergeController") + + return &genericMergeController[T, E]{ + controllerName: controllerName, + resourceType: zeroE.ResourceDefinition().Type, + namespaceIn: namespaceIn, + namespaceOut: namespaceOut, + mergeFunc: mergeFunc, + } +} + +type genericMergeController[T typed.DeepCopyable[T], E typed.Extension] struct { + controllerName string + resourceType resource.Type + namespaceIn resource.Namespace + namespaceOut resource.Namespace + mergeFunc genericMergeFunc[T, E, typed.Resource[T, E]] +} + +func (ctrl *genericMergeController[T, E]) Name() string { + return ctrl.controllerName +} + +func (ctrl *genericMergeController[T, E]) Inputs() []controller.Input { + return []controller.Input{ + { + Namespace: ctrl.namespaceIn, + Type: ctrl.resourceType, + Kind: controller.InputWeak, + }, + { + Namespace: ctrl.namespaceOut, + Type: ctrl.resourceType, + Kind: controller.InputDestroyReady, + }, + } +} + +func (ctrl *genericMergeController[T, E]) Outputs() []controller.Output { + return []controller.Output{ + { + Type: ctrl.resourceType, + Kind: controller.OutputShared, + }, + } +} + +//nolint:gocyclo +func (ctrl *genericMergeController[T, E]) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error { + for { + select { + case <-ctx.Done(): + return nil + case <-r.EventCh(): + } + + type R = typed.Resource[T, E] + + // list source network configuration resources + in, err := safe.ReaderList[*R](ctx, r, resource.NewMetadata(ctrl.namespaceIn, ctrl.resourceType, "", resource.VersionUndefined)) + if err != nil { + return fmt.Errorf("error listing source network resources: %w", err) + } + + merged := ctrl.mergeFunc(logger, in) + + // cleanup resources, detecting conflicts on the way + out, err := safe.ReaderList[*R](ctx, r, resource.NewMetadata(ctrl.namespaceOut, ctrl.resourceType, "", resource.VersionUndefined)) + if err != nil { + return fmt.Errorf("error listing output resources: %w", err) + } + + for res := range out.All() { + shouldBeDestroyed := false + if _, ok := merged[res.Metadata().ID()]; !ok { + shouldBeDestroyed = true + } + + isTearingDown := res.Metadata().Phase() == resource.PhaseTearingDown + + if shouldBeDestroyed || isTearingDown { + var okToDestroy bool + + okToDestroy, err = r.Teardown(ctx, res.Metadata()) + if err != nil { + return fmt.Errorf("error cleaning up addresses: %w", err) + } + + if okToDestroy { + if err = r.Destroy(ctx, res.Metadata()); err != nil { + return fmt.Errorf("error cleaning up addresses: %w", err) + } + } else if !shouldBeDestroyed { + // resource is not ready to be destroyed yet, skip it + delete(merged, res.Metadata().ID()) + } + } + } + + var zeroT T + + for id, spec := range merged { + if err = safe.WriterModify(ctx, r, + typed.NewResource[T, E](resource.NewMetadata(ctrl.namespaceOut, ctrl.resourceType, id, resource.VersionUndefined), zeroT), + func(r *R) error { + *r.TypedSpec() = *spec + + return nil + }); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } + + logger.Debug("merged spec", zap.String("id", id), zap.Any("spec", spec)) + } + + r.ResetRestartBackoff() + } +} diff --git a/internal/app/machined/pkg/controllers/network/hostname_merge.go b/internal/app/machined/pkg/controllers/network/hostname_merge.go index 551dc24e55..9ee8fde7db 100644 --- a/internal/app/machined/pkg/controllers/network/hostname_merge.go +++ b/internal/app/machined/pkg/controllers/network/hostname_merge.go @@ -6,118 +6,41 @@ package network import ( - "context" - "fmt" - "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// HostnameMergeController merges network.HostnameSpec in network.ConfigNamespace and produces final network.HostnameSpec in network.Namespace. -type HostnameMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *HostnameMergeController) Name() string { - return "network.HostnameMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *HostnameMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.HostnameSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.HostnameSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *HostnameMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.HostnameSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewHostnameMergeController initializes a HostnameMergeController. // -//nolint:gocyclo -func (ctrl *HostnameMergeController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.HostnameSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network addresses: %w", err) - } - - // simply merge by layers, overriding with the next configuration layer - var final network.HostnameSpecSpec - - for _, res := range list.Items { - spec := res.(*network.HostnameSpec) //nolint:forcetypeassert - - if final.Hostname != "" && spec.TypedSpec().ConfigLayer <= final.ConfigLayer { - // skip this spec, as existing one is higher layer - continue - } - - final = *spec.TypedSpec() - } - - if final.Hostname != "" { - if err = safe.WriterModify(ctx, r, network.NewHostnameSpec(network.NamespaceName, network.HostnameID), func(spec *network.HostnameSpec) error { - *spec.TypedSpec() = final - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // conflict - final.Hostname = "" - - r.QueueReconcile() - } else { - return fmt.Errorf("error updating resource: %w", err) +// HostnameMergeController merges network.HostnameSpec in network.ConfigNamespace and produces final network.HostnameSpec in network.Namespace. +func NewHostnameMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.HostnameSpec]) map[resource.ID]*network.HostnameSpecSpec { + // simply merge by layers, overriding with the next configuration layer + var final network.HostnameSpecSpec + + for spec := range list.All() { + if final.Hostname != "" && spec.TypedSpec().ConfigLayer <= final.ConfigLayer { + // skip this spec, as existing one is higher layer + continue } - } - } - - if final.Hostname == "" { - // remove existing - var okToDestroy bool - md := resource.NewMetadata(network.NamespaceName, network.HostnameSpecType, network.HostnameID, resource.VersionUndefined) - - okToDestroy, err = r.Teardown(ctx, md) - if err != nil && !state.IsNotFoundError(err) { - return fmt.Errorf("error cleaning up specs: %w", err) + final = *spec.TypedSpec() } - if okToDestroy { - if err = r.Destroy(ctx, md); err != nil { - return fmt.Errorf("error cleaning up specs: %w", err) + if final.Hostname != "" { + return map[resource.ID]*network.HostnameSpecSpec{ + network.HostnameID: &final, } } - } - r.ResetRestartBackoff() - } + return nil + }, + ) } diff --git a/internal/app/machined/pkg/controllers/network/hostname_merge_test.go b/internal/app/machined/pkg/controllers/network/hostname_merge_test.go index 846118b2aa..e770e9cd79 100644 --- a/internal/app/machined/pkg/controllers/network/hostname_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/hostname_merge_test.go @@ -46,7 +46,7 @@ func (suite *HostnameMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.HostnameMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewHostnameMergeController())) suite.startRuntime() } diff --git a/internal/app/machined/pkg/controllers/network/link_merge.go b/internal/app/machined/pkg/controllers/network/link_merge.go index ff660f1619..9b673a9622 100644 --- a/internal/app/machined/pkg/controllers/network/link_merge.go +++ b/internal/app/machined/pkg/controllers/network/link_merge.go @@ -7,141 +7,47 @@ package network import ( "cmp" - "context" - "fmt" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// LinkMergeController merges network.LinkSpec in network.ConfigNamespace and produces final network.LinkSpec in network.Namespace. -type LinkMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *LinkMergeController) Name() string { - return "network.LinkMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *LinkMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.LinkSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.LinkSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *LinkMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.LinkSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewLinkMergeController initializes a LinkMergeController. // -//nolint:gocyclo -func (ctrl *LinkMergeController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := safe.ReaderList[*network.LinkSpec](ctx, r, resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network routes: %w", err) - } - - // sort by link name, configuration layer - list.SortFunc(func(left, right *network.LinkSpec) int { - if res := cmp.Compare(left.TypedSpec().Name, right.TypedSpec().Name); res != 0 { - return res - } - - return cmp.Compare(left.TypedSpec().ConfigLayer, right.TypedSpec().ConfigLayer) - }) - - // build final link definition merging multiple layers - links := make(map[string]*network.LinkSpecSpec, list.Len()) - - for link := range list.All() { - id := network.LinkID(link.TypedSpec().Name) - - existing, ok := links[id] - if !ok { - links[id] = link.TypedSpec() - } else if err = existing.Merge(link.TypedSpec()); err != nil { - logger.Warn("error merging links", zap.Error(err)) - } - } - - conflictsDetected := 0 - - for id, link := range links { - if err = safe.WriterModify(ctx, r, network.NewLinkSpec(network.NamespaceName, id), func(l *network.LinkSpec) error { - *l.TypedSpec() = *link - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // phase conflict, resource is being torn down, skip updating it and trigger reconcile - // later by failing the - conflictsDetected++ - - delete(links, id) - } else { - return fmt.Errorf("error updating resource: %w", err) +// LinkMergeController merges network.LinkSpec in network.ConfigNamespace and produces final network.AddressSpec in network.Namespace. +func NewLinkMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.LinkSpec]) map[resource.ID]*network.LinkSpecSpec { + // sort by link name, configuration layer + list.SortFunc(func(left, right *network.LinkSpec) int { + if res := cmp.Compare(left.TypedSpec().Name, right.TypedSpec().Name); res != 0 { + return res } - } - - logger.Debug("merged link spec", zap.String("id", id), zap.Any("spec", link)) - } - // list link for cleanup - list, err = safe.ReaderList[*network.LinkSpec](ctx, r, resource.NewMetadata(network.NamespaceName, network.LinkSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing resources: %w", err) - } + return cmp.Compare(left.TypedSpec().ConfigLayer, right.TypedSpec().ConfigLayer) + }) - for res := range list.All() { - if _, ok := links[res.Metadata().ID()]; !ok { - var okToDestroy bool + // build final link definition merging multiple layers + links := make(map[string]*network.LinkSpecSpec, list.Len()) - okToDestroy, err = r.Teardown(ctx, res.Metadata()) - if err != nil { - return fmt.Errorf("error cleaning up addresses: %w", err) - } + for link := range list.All() { + id := network.LinkID(link.TypedSpec().Name) - if okToDestroy { - if err = r.Destroy(ctx, res.Metadata()); err != nil { - return fmt.Errorf("error cleaning up addresses: %w", err) - } + existing, ok := links[id] + if !ok { + links[id] = link.TypedSpec() + } else if err := existing.Merge(link.TypedSpec()); err != nil { + logger.Warn("error merging links", zap.Error(err)) } } - } - - if conflictsDetected > 0 { - return fmt.Errorf("%d conflict(s) detected", conflictsDetected) - } - r.ResetRestartBackoff() - } + return links + }, + ) } diff --git a/internal/app/machined/pkg/controllers/network/link_merge_test.go b/internal/app/machined/pkg/controllers/network/link_merge_test.go index 124174f032..bf5141689e 100644 --- a/internal/app/machined/pkg/controllers/network/link_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/link_merge_test.go @@ -49,7 +49,7 @@ func (suite *LinkMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.LinkMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewLinkMergeController())) suite.startRuntime() } @@ -334,6 +334,10 @@ func (suite *LinkMergeSuite) TestMergeWireguard() { asrt.Equal(1234, r.TypedSpec().Wireguard.ListenPort) asrt.Len(r.TypedSpec().Wireguard.Peers, 2) + if len(r.TypedSpec().Wireguard.Peers) != 2 { + return + } + asrt.Equal( network.WireguardPeer{ PublicKey: "RXdQkMTD1Jcxd/Wizr9k8syw8ANs57l5jTormDVHAVs=", diff --git a/internal/app/machined/pkg/controllers/network/operator_merge.go b/internal/app/machined/pkg/controllers/network/operator_merge.go index 4b2dadd270..fb402b417f 100644 --- a/internal/app/machined/pkg/controllers/network/operator_merge.go +++ b/internal/app/machined/pkg/controllers/network/operator_merge.go @@ -2,138 +2,41 @@ // 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 network provides controllers which manage network resources. -// -//nolint:dupl package network import ( - "context" - "fmt" - "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// OperatorMergeController merges network.OperatorSpec in network.ConfigNamespace and produces final network.OperatorSpec in network.Namespace. -type OperatorMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *OperatorMergeController) Name() string { - return "network.OperatorMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *OperatorMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.OperatorSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.OperatorSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *OperatorMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.OperatorSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewOperatorMergeController initializes a OperatorMergeController. // -//nolint:gocyclo -func (ctrl *OperatorMergeController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network operators: %w", err) - } - - // operator is allowed as long as it's not duplicate, for duplicate higher layer takes precedence - operators := map[string]*network.OperatorSpec{} - - for _, res := range list.Items { - operator := res.(*network.OperatorSpec) //nolint:forcetypeassert - id := network.OperatorID(operator.TypedSpec().Operator, operator.TypedSpec().LinkName) - - existing, ok := operators[id] - if ok && existing.TypedSpec().ConfigLayer > operator.TypedSpec().ConfigLayer { - // skip this operator, as existing one is higher layer - continue - } - - operators[id] = operator - } - - conflictsDetected := 0 - - for id, operator := range operators { - if err = safe.WriterModify(ctx, r, network.NewOperatorSpec(network.NamespaceName, id), func(op *network.OperatorSpec) error { - *op.TypedSpec() = *operator.TypedSpec() - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // phase conflict, resource is being torn down, skip updating it and trigger reconcile - // later by failing the loop after all processing is done - conflictsDetected++ - - delete(operators, id) - } else { - return fmt.Errorf("error updating resource: %w", err) - } - } - } - - // list operators for cleanup - list, err = r.List(ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing resources: %w", err) - } - - for _, res := range list.Items { - if _, ok := operators[res.Metadata().ID()]; !ok { - var okToDestroy bool - - okToDestroy, err = r.Teardown(ctx, res.Metadata()) - if err != nil { - return fmt.Errorf("error cleaning up operators: %w", err) +// OperatorMergeController merges network.OperatorSpec in network.ConfigNamespace and produces final network.OperatorSpec in network.Namespace. +func NewOperatorMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.OperatorSpec]) map[resource.ID]*network.OperatorSpecSpec { + // operator is allowed as long as it's not duplicate, for duplicate higher layer takes precedence + operators := map[string]*network.OperatorSpecSpec{} + + for operator := range list.All() { + id := network.OperatorID(operator.TypedSpec().Operator, operator.TypedSpec().LinkName) + + existing, ok := operators[id] + if ok && existing.ConfigLayer > operator.TypedSpec().ConfigLayer { + // skip this operator, as existing one is higher layer + continue } - if okToDestroy { - if err = r.Destroy(ctx, res.Metadata()); err != nil { - return fmt.Errorf("error cleaning up operators: %w", err) - } - } + operators[id] = operator.TypedSpec() } - } - if conflictsDetected > 0 { - return fmt.Errorf("%d conflict(s) detected", conflictsDetected) - } - - r.ResetRestartBackoff() - } + return operators + }, + ) } diff --git a/internal/app/machined/pkg/controllers/network/operator_merge_test.go b/internal/app/machined/pkg/controllers/network/operator_merge_test.go index 0133154f13..dd0db29219 100644 --- a/internal/app/machined/pkg/controllers/network/operator_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/operator_merge_test.go @@ -47,7 +47,7 @@ func (suite *OperatorMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.OperatorMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewOperatorMergeController())) suite.startRuntime() } diff --git a/internal/app/machined/pkg/controllers/network/resolver_merge.go b/internal/app/machined/pkg/controllers/network/resolver_merge.go index 4f49796d50..cf61cd34b7 100644 --- a/internal/app/machined/pkg/controllers/network/resolver_merge.go +++ b/internal/app/machined/pkg/controllers/network/resolver_merge.go @@ -7,132 +7,58 @@ package network import ( "cmp" - "context" - "fmt" "net/netip" "slices" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/gen/xslices" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// ResolverMergeController merges network.ResolverSpec in network.ConfigNamespace and produces final network.ResolverSpec in network.Namespace. -type ResolverMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *ResolverMergeController) Name() string { - return "network.ResolverMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *ResolverMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.ResolverSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.ResolverSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *ResolverMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.ResolverSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewResolverMergeController initializes a ResolverMergeController. // -//nolint:gocyclo -func (ctrl *ResolverMergeController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := safe.ReaderList[*network.ResolverSpec](ctx, r, resource.NewMetadata(network.ConfigNamespaceName, network.ResolverSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network addresses: %w", err) - } - - // sort by config layer - list.SortFunc(func(l, r *network.ResolverSpec) int { - return cmp.Compare(l.TypedSpec().ConfigLayer, r.TypedSpec().ConfigLayer) - }) - - // simply merge by layers, overriding with the next configuration layer - var final network.ResolverSpecSpec - - for res := range list.All() { - spec := res.TypedSpec() - - final.SearchDomains = slices.Insert(final.SearchDomains, 0, spec.SearchDomains...) - - if spec.ConfigLayer == final.ConfigLayer { - // simply append server lists on the same layer - final.DNSServers = append(final.DNSServers, spec.DNSServers...) - } else { - // otherwise, do a smart merge across IPv4/IPv6 - final.ConfigLayer = spec.ConfigLayer - mergeDNSServers(&final.DNSServers, spec.DNSServers) - } - } - - if final.DNSServers != nil { - if err = safe.WriterModify(ctx, r, network.NewResolverSpec(network.NamespaceName, network.ResolverID), func(spec *network.ResolverSpec) error { - *spec.TypedSpec() = final - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // conflict - final.DNSServers = nil - - r.QueueReconcile() +// ResolverMergeController merges network.ResolverSpec in network.ConfigNamespace and produces final network.ResolverSpec in network.Namespace. +func NewResolverMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.ResolverSpec]) map[resource.ID]*network.ResolverSpecSpec { + // sort by config layer + list.SortFunc(func(l, r *network.ResolverSpec) int { + return cmp.Compare(l.TypedSpec().ConfigLayer, r.TypedSpec().ConfigLayer) + }) + + // simply merge by layers, overriding with the next configuration layer + var final network.ResolverSpecSpec + + for res := range list.All() { + spec := res.TypedSpec() + + final.SearchDomains = slices.Insert(final.SearchDomains, 0, spec.SearchDomains...) + + if spec.ConfigLayer == final.ConfigLayer { + // simply append server lists on the same layer + final.DNSServers = append(final.DNSServers, spec.DNSServers...) } else { - return fmt.Errorf("error updating resource: %w", err) + // otherwise, do a smart merge across IPv4/IPv6 + final.ConfigLayer = spec.ConfigLayer + mergeDNSServers(&final.DNSServers, spec.DNSServers) } } - } - - if final.DNSServers == nil { - // remove existing - var okToDestroy bool - - md := resource.NewMetadata(network.NamespaceName, network.ResolverSpecType, network.ResolverID, resource.VersionUndefined) - - okToDestroy, err = r.Teardown(ctx, md) - if err != nil && !state.IsNotFoundError(err) { - return fmt.Errorf("error cleaning up specs: %w", err) - } - if okToDestroy { - if err = r.Destroy(ctx, md); err != nil { - return fmt.Errorf("error cleaning up specs: %w", err) + if final.DNSServers != nil { + return map[resource.ID]*network.ResolverSpecSpec{ + network.ResolverID: &final, } } - } - r.ResetRestartBackoff() - } + return nil + }, + ) } func mergeDNSServers(dst *[]netip.Addr, src []netip.Addr) { diff --git a/internal/app/machined/pkg/controllers/network/resolver_merge_test.go b/internal/app/machined/pkg/controllers/network/resolver_merge_test.go index 88144a7166..0b85737e4e 100644 --- a/internal/app/machined/pkg/controllers/network/resolver_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/resolver_merge_test.go @@ -49,7 +49,7 @@ func (suite *ResolverMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.ResolverMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewResolverMergeController())) suite.startRuntime() } diff --git a/internal/app/machined/pkg/controllers/network/route_merge.go b/internal/app/machined/pkg/controllers/network/route_merge.go index 9aeb852c02..ec14c84ab6 100644 --- a/internal/app/machined/pkg/controllers/network/route_merge.go +++ b/internal/app/machined/pkg/controllers/network/route_merge.go @@ -2,136 +2,41 @@ // 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 network provides controllers which manage network resources. package network import ( - "context" - "fmt" - "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// RouteMergeController merges network.RouteSpec in network.ConfigNamespace and produces final network.RouteSpec in network.Namespace. -type RouteMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *RouteMergeController) Name() string { - return "network.RouteMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *RouteMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.RouteSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.RouteSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *RouteMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.RouteSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewRouteMergeController initializes a RouteMergeController. // -//nolint:gocyclo -func (ctrl *RouteMergeController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.RouteSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network routes: %w", err) - } - - // route is allowed as long as it's not duplicate, for duplicate higher layer takes precedence - routes := map[string]*network.RouteSpec{} - - for _, res := range list.Items { - route := res.(*network.RouteSpec) //nolint:forcetypeassert - id := network.RouteID(route.TypedSpec().Table, route.TypedSpec().Family, route.TypedSpec().Destination, route.TypedSpec().Gateway, route.TypedSpec().Priority, route.TypedSpec().OutLinkName) - - existing, ok := routes[id] - if ok && existing.TypedSpec().ConfigLayer > route.TypedSpec().ConfigLayer { - // skip this route, as existing one is higher layer - continue - } - - routes[id] = route - } - - conflictsDetected := 0 - - for id, route := range routes { - if err = safe.WriterModify(ctx, r, network.NewRouteSpec(network.NamespaceName, id), func(rt *network.RouteSpec) error { - *rt.TypedSpec() = *route.TypedSpec() - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // phase conflict, resource is being torn down, skip updating it and trigger reconcile - // later by failing the - conflictsDetected++ - - delete(routes, id) - } else { - return fmt.Errorf("error updating resource: %w", err) - } - } - } - - // list routes for cleanup - list, err = r.List(ctx, resource.NewMetadata(network.NamespaceName, network.RouteSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing resources: %w", err) - } - - for _, res := range list.Items { - if _, ok := routes[res.Metadata().ID()]; !ok { - var okToDestroy bool - - okToDestroy, err = r.Teardown(ctx, res.Metadata()) - if err != nil { - return fmt.Errorf("error cleaning up routes: %w", err) +// RouteMergeController merges network.RouteSpec in network.ConfigNamespace and produces final network.RouteSpec in network.Namespace. +func NewRouteMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.RouteSpec]) map[resource.ID]*network.RouteSpecSpec { + // route is allowed as long as it's not duplicate, for duplicate higher layer takes precedence + routes := map[string]*network.RouteSpecSpec{} + + for route := range list.All() { + id := network.RouteID(route.TypedSpec().Table, route.TypedSpec().Family, route.TypedSpec().Destination, route.TypedSpec().Gateway, route.TypedSpec().Priority, route.TypedSpec().OutLinkName) + + existing, ok := routes[id] + if ok && existing.ConfigLayer > route.TypedSpec().ConfigLayer { + // skip this route, as existing one is higher layer + continue } - if okToDestroy { - if err = r.Destroy(ctx, res.Metadata()); err != nil { - return fmt.Errorf("error cleaning up routes: %w", err) - } - } + routes[id] = route.TypedSpec() } - } - - if conflictsDetected > 0 { - return fmt.Errorf("%d conflict(s) detected", conflictsDetected) - } - r.ResetRestartBackoff() - } + return routes + }, + ) } diff --git a/internal/app/machined/pkg/controllers/network/route_merge_test.go b/internal/app/machined/pkg/controllers/network/route_merge_test.go index 970abead3b..e3f0bb4df6 100644 --- a/internal/app/machined/pkg/controllers/network/route_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/route_merge_test.go @@ -49,7 +49,7 @@ func (suite *RouteMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.RouteMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewRouteMergeController())) suite.startRuntime() } diff --git a/internal/app/machined/pkg/controllers/network/timeserver_merge.go b/internal/app/machined/pkg/controllers/network/timeserver_merge.go index 6c0b825772..fb38b51223 100644 --- a/internal/app/machined/pkg/controllers/network/timeserver_merge.go +++ b/internal/app/machined/pkg/controllers/network/timeserver_merge.go @@ -6,124 +6,47 @@ package network import ( - "context" - "fmt" - "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// TimeServerMergeController merges network.TimeServerSpec in network.ConfigNamespace and produces final network.TimeServerSpec in network.Namespace. -type TimeServerMergeController struct{} - -// Name implements controller.Controller interface. -func (ctrl *TimeServerMergeController) Name() string { - return "network.TimeServerMergeController" -} - -// Inputs implements controller.Controller interface. -func (ctrl *TimeServerMergeController) Inputs() []controller.Input { - return []controller.Input{ - { - Namespace: network.ConfigNamespaceName, - Type: network.TimeServerSpecType, - Kind: controller.InputWeak, - }, - { - Namespace: network.NamespaceName, - Type: network.TimeServerSpecType, - Kind: controller.InputDestroyReady, - }, - } -} - -// Outputs implements controller.Controller interface. -func (ctrl *TimeServerMergeController) Outputs() []controller.Output { - return []controller.Output{ - { - Type: network.TimeServerSpecType, - Kind: controller.OutputShared, - }, - } -} - -// Run implements controller.Controller interface. +// NewTimeServerMergeController initializes a TimeServerMergeController. // -//nolint:gocyclo -func (ctrl *TimeServerMergeController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { - for { - select { - case <-ctx.Done(): - return nil - case <-r.EventCh(): - } - - // list source network configuration resources - list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.TimeServerSpecType, "", resource.VersionUndefined)) - if err != nil { - return fmt.Errorf("error listing source network addresses: %w", err) - } - - // simply merge by layers, overriding with the next configuration layer - var final network.TimeServerSpecSpec - - for _, res := range list.Items { - spec := res.(*network.TimeServerSpec) //nolint:forcetypeassert - - if final.NTPServers != nil && spec.TypedSpec().ConfigLayer < final.ConfigLayer { - // skip this spec, as existing one is higher layer - continue - } - - if spec.TypedSpec().ConfigLayer == final.ConfigLayer { - // merge server lists on the same level - final.NTPServers = append(final.NTPServers, spec.TypedSpec().NTPServers...) - } else { - // otherwise, replace the lists - final = *spec.TypedSpec() - } - } - - if final.NTPServers != nil { - if err = safe.WriterModify(ctx, r, network.NewTimeServerSpec(network.NamespaceName, network.TimeServerID), func(spec *network.TimeServerSpec) error { - *spec.TypedSpec() = final - - return nil - }); err != nil { - if state.IsPhaseConflictError(err) { - // conflict - final.NTPServers = nil +// TimeServerMergeController merges network.TimeServerSpec in network.ConfigNamespace and produces final network.TimeServerSpec in network.Namespace. +func NewTimeServerMergeController() controller.Controller { + return GenericMergeController( + network.ConfigNamespaceName, + network.NamespaceName, + func(logger *zap.Logger, list safe.List[*network.TimeServerSpec]) map[resource.ID]*network.TimeServerSpecSpec { + // simply merge by layers, overriding with the next configuration layer + var final network.TimeServerSpecSpec + + for spec := range list.All() { + if final.NTPServers != nil && spec.TypedSpec().ConfigLayer < final.ConfigLayer { + // skip this spec, as existing one is higher layer + continue + } - r.QueueReconcile() + if spec.TypedSpec().ConfigLayer == final.ConfigLayer { + // merge server lists on the same level + final.NTPServers = append(final.NTPServers, spec.TypedSpec().NTPServers...) } else { - return fmt.Errorf("error updating resource: %w", err) + // otherwise, replace the lists + final = *spec.TypedSpec() } } - } - - if final.NTPServers == nil { - // remove existing - var okToDestroy bool - md := resource.NewMetadata(network.NamespaceName, network.TimeServerSpecType, network.TimeServerID, resource.VersionUndefined) - - okToDestroy, err = r.Teardown(ctx, md) - if err != nil && !state.IsNotFoundError(err) { - return fmt.Errorf("error cleaning up specs: %w", err) - } - - if okToDestroy { - if err = r.Destroy(ctx, md); err != nil { - return fmt.Errorf("error cleaning up specs: %w", err) + if final.NTPServers != nil { + return map[resource.ID]*network.TimeServerSpecSpec{ + network.TimeServerID: &final, } } - } - r.ResetRestartBackoff() - } + return nil + }, + ) } diff --git a/internal/app/machined/pkg/controllers/network/timeserver_merge_test.go b/internal/app/machined/pkg/controllers/network/timeserver_merge_test.go index f1470662ca..17d2d033a7 100644 --- a/internal/app/machined/pkg/controllers/network/timeserver_merge_test.go +++ b/internal/app/machined/pkg/controllers/network/timeserver_merge_test.go @@ -47,7 +47,7 @@ func (suite *TimeServerMergeSuite) SetupTest() { suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T())) suite.Require().NoError(err) - suite.Require().NoError(suite.runtime.RegisterController(&netctrl.TimeServerMergeController{})) + suite.Require().NoError(suite.runtime.RegisterController(netctrl.NewTimeServerMergeController())) suite.startRuntime() } diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go index 08ebbaa092..c0ca3c76e1 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go @@ -224,7 +224,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.AddressEventController{ V1Alpha1Events: ctrl.v1alpha1Runtime.Events(), }, - &network.AddressMergeController{}, + network.NewAddressMergeController(), &network.AddressSpecController{}, &network.AddressStatusController{}, &network.DeviceConfigController{}, @@ -242,14 +242,14 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.HostnameConfigController{ Cmdline: procfs.ProcCmdline(), }, - &network.HostnameMergeController{}, + network.NewHostnameMergeController(), &network.HostnameSpecController{ V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(), }, &network.LinkConfigController{ Cmdline: procfs.ProcCmdline(), }, - &network.LinkMergeController{}, + network.NewLinkMergeController(), &network.LinkSpecController{}, &network.LinkStatusController{}, &network.NfTablesChainConfigController{}, @@ -259,7 +259,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.OperatorConfigController{ Cmdline: procfs.ProcCmdline(), }, - &network.OperatorMergeController{}, + network.NewOperatorMergeController(), &network.OperatorSpecController{ V1alpha1Platform: ctrl.v1alpha1Runtime.State().Platform(), State: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(), @@ -275,12 +275,12 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.ResolverConfigController{ Cmdline: procfs.ProcCmdline(), }, - &network.ResolverMergeController{}, + network.NewResolverMergeController(), &network.ResolverSpecController{}, &network.RouteConfigController{ Cmdline: procfs.ProcCmdline(), }, - &network.RouteMergeController{}, + network.NewRouteMergeController(), &network.RouteSpecController{}, &network.RouteStatusController{}, &network.StatusController{ @@ -289,7 +289,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.TimeServerConfigController{ Cmdline: procfs.ProcCmdline(), }, - &network.TimeServerMergeController{}, + network.NewTimeServerMergeController(), &network.TimeServerSpecController{}, &perf.StatsController{}, cri.NewRegistriesConfigController(),