From d604d597b0065016ee2ed7c907b4b84bd78fc89d Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 8 Nov 2024 16:54:40 -0500 Subject: [PATCH 01/17] Fixes unexpected mp0 route removal during start up Routes via mp0 were being deleted on every ovnkube-node restart: [root@ovn-worker ~]# ip monitor route Deleted 192.72.3.0/24 dev ovn-k8s-mp0 proto kernel scope link src 192.72.3.2 Deleted broadcast 192.72.3.255 dev ovn-k8s-mp0 table local proto kernel scope link src 192.72.3.2 Deleted local 192.72.3.2 dev ovn-k8s-mp0 table local proto kernel scope host src 192.72.3.2 local 192.72.3.2 dev ovn-k8s-mp0 table local proto kernel scope host src 192.72.3.2 broadcast 192.72.3.255 dev ovn-k8s-mp0 table local proto kernel scope link src 192.72.3.2 This causes traffic outage during upgrade, as well as other unwanted side effects when pod-destined traffic is routed via default gateway route in the host. This is especially disruptive in local gateway mode. This patch removes the teardown, and then makes the synchronization of addresses and routes more robust, so that we can safely handle changes to MTU or mp0 addresses. Signed-off-by: Tim Rozet --- .../pkg/node/management-port_linux.go | 45 ++---- go-controller/pkg/util/net_linux.go | 67 ++++++++- go-controller/pkg/util/net_linux_unit_test.go | 129 +++++++++++++++++- 3 files changed, 200 insertions(+), 41 deletions(-) diff --git a/go-controller/pkg/node/management-port_linux.go b/go-controller/pkg/node/management-port_linux.go index 6b917a5279..eba68ed9a5 100644 --- a/go-controller/pkg/node/management-port_linux.go +++ b/go-controller/pkg/node/management-port_linux.go @@ -160,48 +160,27 @@ func tearDownInterfaceIPConfig(link netlink.Link, ipt4, ipt6 util.IPTablesHelper return nil } -func tearDownManagementPortConfig(mpcfg *managementPortConfig) error { - // for the initial setup we need to start from the clean slate, so flush - // all (non-LL) addresses on this link, routes through this link, and - // finally any IPtable rules for this link. - var ipt4, ipt6 util.IPTablesHelper - - if mpcfg.ipv4 != nil { - ipt4 = mpcfg.ipv4.ipt - } - if mpcfg.ipv6 != nil { - ipt6 = mpcfg.ipv6.ipt - } - return tearDownInterfaceIPConfig(mpcfg.link, ipt4, ipt6) -} - func setupManagementPortIPFamilyConfig(routeManager *routemanager.Controller, mpcfg *managementPortConfig, cfg *managementPortIPFamilyConfig) ([]string, error) { var warnings []string var err error var exists bool - if exists, err = util.LinkAddrExist(mpcfg.link, cfg.ifAddr); err == nil && !exists { - // we should log this so that one can debug as to why addresses are - // disappearing - warnings = append(warnings, fmt.Sprintf("missing IP address %s on the interface %s, adding it...", - cfg.ifAddr, mpcfg.ifName)) - err = util.LinkAddrAdd(mpcfg.link, cfg.ifAddr, 0, 0, 0) - } + // synchronize IP addresses, removing undesired addresses + // should also remove routes specifying those undesired addresses + err = util.SyncAddresses(mpcfg.link, []*net.IPNet{cfg.ifAddr}) if err != nil { return warnings, err } + // now check for addition of any missing routes for _, subnet := range cfg.allSubnets { - exists, err = util.LinkRouteExists(mpcfg.link, cfg.gwIP, subnet) - if err != nil { - return warnings, err - } - if exists { - continue + route, err := util.LinkRouteGetByDstAndGw(mpcfg.link, cfg.gwIP, subnet) + if err != nil || route == nil { + // we need to warn so that it can be debugged as to why routes are incorrect + warnings = append(warnings, fmt.Sprintf("missing or unable to find route entry for subnet %s "+ + "via gateway %s on link %v with MTU: %d", subnet, cfg.gwIP, mpcfg.ifName, config.Default.RoutableMTU)) } - // we need to warn so that it can be debugged as to why routes are disappearing - warnings = append(warnings, fmt.Sprintf("missing route entry for subnet %s via gateway %s on link %v", - subnet, cfg.gwIP, mpcfg.ifName)) + subnetCopy := *subnet err = routeManager.Add(netlink.Route{LinkIndex: mpcfg.link.Attrs().Index, Gw: cfg.gwIP, Dst: &subnetCopy, MTU: config.Default.RoutableMTU}) if err != nil { @@ -307,10 +286,6 @@ func createPlatformManagementPort(routeManager *routemanager.Controller, interfa return nil, err } - if err = tearDownManagementPortConfig(cfg); err != nil { - return nil, err - } - if _, err = setupManagementPortConfig(routeManager, cfg); err != nil { return nil, err } diff --git a/go-controller/pkg/util/net_linux.go b/go-controller/pkg/util/net_linux.go index 0a2cd53f3f..cbe71abb8c 100644 --- a/go-controller/pkg/util/net_linux.go +++ b/go-controller/pkg/util/net_linux.go @@ -249,6 +249,67 @@ func LinkAddrFlush(link netlink.Link) error { return nil } +// SyncAddresses ensures the link has the provided addresses only +// Ignores IPv6 LLA +// addresses should all be of the same family +func SyncAddresses(link netlink.Link, addresses []*net.IPNet) error { + if len(addresses) == 0 { + return nil + } + firstFamily := getFamily(addresses[0].IP) + for _, addr := range addresses[1:] { + if getFamily(addr.IP) != firstFamily { + return fmt.Errorf("all addresses are not the same family: %#v", addresses) + } + } + + addrs, err := netLinkOps.AddrList(link, firstFamily) + if err != nil { + return fmt.Errorf("failed to list addresses for the link %s: %v", + link.Attrs().Name, err) + } + + // desired addresses - true if already exist + matched := map[*net.IPNet]bool{} + for _, desiredAddr := range addresses { + matched[desiredAddr] = false + } + + // cycle through found addresses + for _, addr := range addrs { + if utilnet.IsIPv6(addr.IP) && addr.IP.IsLinkLocalUnicast() { + continue + } + + exists := false + for _, desiredAddr := range addresses { + if addr.IPNet.String() == desiredAddr.String() { + matched[desiredAddr] = true + exists = true + break + } + } + + // found address is not in desired list, remove it + if !exists { + if err := LinkAddrDel(link, addr.IPNet); err != nil { + return err + } + } + } + + // cycle through leftover addresses to add + for addr, alreadyExists := range matched { + if !alreadyExists { + if err := LinkAddrAdd(link, addr, 0, 0, 0); err != nil { + return err + } + } + } + + return nil +} + // LinkAddrExist returns true if the given address is present on the link func LinkAddrExist(link netlink.Link, address *net.IPNet) (bool, error) { addrs, err := netLinkOps.AddrList(link, getFamily(address.IP)) @@ -415,10 +476,10 @@ func LinkRouteGetFilteredRoute(routeFilter *netlink.Route, filterMask uint64) (* return &routes[0], nil } -// LinkRouteExists checks for existence of routes for the given subnet through gwIPStr -func LinkRouteExists(link netlink.Link, gwIP net.IP, subnet *net.IPNet) (bool, error) { +// LinkRouteGetByDstAndGw checks for existence of routes for the given subnet through gwIPStr +func LinkRouteGetByDstAndGw(link netlink.Link, gwIP net.IP, subnet *net.IPNet) (*netlink.Route, error) { route, err := LinkRouteGetFilteredRoute(filterRouteByDstAndGw(link, subnet, gwIP)) - return route != nil, err + return route, err } // LinkNeighDel deletes an ip binding for a given link diff --git a/go-controller/pkg/util/net_linux_unit_test.go b/go-controller/pkg/util/net_linux_unit_test.go index d6ca4764c8..b7593ad5c3 100644 --- a/go-controller/pkg/util/net_linux_unit_test.go +++ b/go-controller/pkg/util/net_linux_unit_test.go @@ -314,6 +314,129 @@ func TestLinkAddrExist(t *testing.T) { } } +func TestSyncAddresses(t *testing.T) { + mockNetLinkOps := new(mocks.NetLinkOps) + mockLink := new(netlink_mocks.Link) + // below is defined in net_linux.go + netLinkOps = mockNetLinkOps + existingIPNet := netlink.Addr{ + IPNet: ovntest.MustParseIPNet("192.168.1.15/24"), + } + undesiredExistingIPNet := netlink.Addr{ + IPNet: ovntest.MustParseIPNet("123.123.123.15/24"), + } + undesiredExistingIPNet2 := netlink.Addr{ + IPNet: ovntest.MustParseIPNet("123.123.124.15/24"), + } + linkLocalAddr := netlink.Addr{ + IPNet: ovntest.MustParseIPNet("fe80::210:5aff:feaa:20a2/64"), + } + + tests := []struct { + desc string + inputLink netlink.Link + inputNewAddrs []*net.IPNet + errExp bool + onRetArgsNetLinkLibOpers []ovntest.TestifyMockHelper + onRetArgsLinkIfaceOpers []ovntest.TestifyMockHelper + }{ + { + desc: "specifying multiple address families fails", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("192.168.1.15/24"), ovntest.MustParseIPNet("6b35:d6d1:5789:1b33:8ad4:866c:78c1:a085/128")}, + errExp: true, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{}, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{}, + }, + { + desc: "link address list failure causes error", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("192.168.1.15/24")}, + errExp: true, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "AddrList", OnCallMethodArgType: []string{"*mocks.Link", "int"}, RetArgList: []interface{}{nil, fmt.Errorf("mock error")}}, + }, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "Attrs", OnCallMethodArgType: []string{}, RetArgList: []interface{}{&netlink.LinkAttrs{Name: "testIfaceName"}}}, + }, + }, + { + desc: "new non-existent address should be added", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("192.168.1.15/24")}, + errExp: false, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "AddrList", OnCallMethodArgType: []string{"*mocks.Link", "int"}, RetArgList: []interface{}{[]netlink.Addr{}, nil}}, + {OnCallMethodName: "AddrAdd", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + }, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{}, + }, + { + desc: "address that already exists should not be added", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("192.168.1.15/24")}, + errExp: false, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "AddrList", OnCallMethodArgType: []string{"*mocks.Link", "int"}, RetArgList: []interface{}{[]netlink.Addr{existingIPNet}, nil}}, + }, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{}, + }, + { + desc: "address should be added while undesired address should be removed", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("192.168.1.15/24")}, + errExp: false, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "AddrList", OnCallMethodArgType: []string{"*mocks.Link", "int"}, RetArgList: []interface{}{[]netlink.Addr{undesiredExistingIPNet}, nil}}, + {OnCallMethodName: "AddrDel", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + {OnCallMethodName: "AddrAdd", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + }, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{}, + }, + { + desc: "multiple addresses should be added while multiple undesired addresses should be removed", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("192.168.1.15/24"), ovntest.MustParseIPNet("192.168.1.16/24")}, + errExp: false, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "AddrList", OnCallMethodArgType: []string{"*mocks.Link", "int"}, RetArgList: []interface{}{[]netlink.Addr{undesiredExistingIPNet, undesiredExistingIPNet2}, nil}}, + {OnCallMethodName: "AddrDel", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + {OnCallMethodName: "AddrDel", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + {OnCallMethodName: "AddrAdd", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + {OnCallMethodName: "AddrAdd", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + }, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{}, + }, + { + desc: "IPv6 LLA addresses should not be touched", + inputLink: mockLink, + inputNewAddrs: []*net.IPNet{ovntest.MustParseIPNet("6b35:d6d1:5789:1b33:8ad4:866c:78c1:a085/128")}, + errExp: false, + onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{ + {OnCallMethodName: "AddrList", OnCallMethodArgType: []string{"*mocks.Link", "int"}, RetArgList: []interface{}{[]netlink.Addr{linkLocalAddr}, nil}}, + {OnCallMethodName: "AddrAdd", OnCallMethodArgType: []string{"*mocks.Link", "*netlink.Addr"}, RetArgList: []interface{}{nil}}, + }, + onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{}, + }, + } + for i, tc := range tests { + t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) { + + ovntest.ProcessMockFnList(&mockNetLinkOps.Mock, tc.onRetArgsNetLinkLibOpers) + ovntest.ProcessMockFnList(&mockLink.Mock, tc.onRetArgsLinkIfaceOpers) + err := SyncAddresses(tc.inputLink, tc.inputNewAddrs) + t.Log(err) + if tc.errExp { + assert.Error(t, err) + } else { + assert.Nil(t, err) + } + mockNetLinkOps.AssertExpectations(t) + mockLink.AssertExpectations(t) + }) + } +} + func TestLinkAddrAdd(t *testing.T) { mockNetLinkOps := new(mocks.NetLinkOps) mockLink := new(netlink_mocks.Link) @@ -674,15 +797,15 @@ func TestLinkRouteExists(t *testing.T) { ovntest.ProcessMockFnList(&mockNetLinkOps.Mock, tc.onRetArgsNetLinkLibOpers) ovntest.ProcessMockFnList(&mockLink.Mock, tc.onRetArgsLinkIfaceOpers) - flag, err := LinkRouteExists(tc.inputLink, tc.inputGwIP, tc.inputSubnet) - t.Log(flag, err) + route, err := LinkRouteGetByDstAndGw(tc.inputLink, tc.inputGwIP, tc.inputSubnet) + t.Log(route, err) if tc.errExp { assert.Error(t, err) } else { assert.Nil(t, err) } if tc.outBoolFlag { - assert.True(t, flag) + assert.True(t, route != nil) } mockNetLinkOps.AssertExpectations(t) mockLink.AssertExpectations(t) From eeb3d60cd4295ad54e5b3b3118c5a4c34a638242 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 12 Nov 2024 20:01:15 -0500 Subject: [PATCH 02/17] [WIP] split per network node info into new CRD Signed-off-by: Tim Rozet --- .../network_cluster_controller.go | 5 +- .../pkg/clustermanager/node/node_allocator.go | 106 ++++++++++++++++-- go-controller/pkg/crd/udnnode/v1/doc.go | 4 + go-controller/pkg/crd/udnnode/v1/register.go | 33 ++++++ go-controller/pkg/crd/udnnode/v1/types.go | 89 +++++++++++++++ go-controller/pkg/factory/factory.go | 16 +++ go-controller/pkg/kube/kube.go | 8 ++ go-controller/pkg/util/kube.go | 2 + 8 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 go-controller/pkg/crd/udnnode/v1/doc.go create mode 100644 go-controller/pkg/crd/udnnode/v1/register.go create mode 100644 go-controller/pkg/crd/udnnode/v1/types.go diff --git a/go-controller/pkg/clustermanager/network_cluster_controller.go b/go-controller/pkg/clustermanager/network_cluster_controller.go index 71603cb627..3dfbba9f40 100644 --- a/go-controller/pkg/clustermanager/network_cluster_controller.go +++ b/go-controller/pkg/clustermanager/network_cluster_controller.go @@ -90,6 +90,7 @@ func newNetworkClusterController(networkIDAllocator idallocator.NamedAllocator, KClient: ovnClient.KubeClient, }, IPAMClaimsClient: ovnClient.IPAMClaimsClient, + UDNNodeClient: ovnClient.UserDefinedNodeClient, } wg := &sync.WaitGroup{} @@ -212,8 +213,8 @@ func (ncc *networkClusterController) init() error { if ncc.hasNodeAllocation() { ncc.retryNodes = ncc.newRetryFramework(factory.NodeType, true) - - ncc.nodeAllocator = node.NewNodeAllocator(networkID, ncc.NetInfo, ncc.watchFactory.NodeCoreInformer().Lister(), ncc.kube, ncc.tunnelIDAllocator) + ncc.nodeAllocator = node.NewNodeAllocator(networkID, ncc.NetInfo, ncc.watchFactory.NodeCoreInformer().Lister(), + ncc.watchFactory.UserDefinedNodeInformer().Lister(), ncc.kube, ncc.tunnelIDAllocator) err := ncc.nodeAllocator.Init() if err != nil { return fmt.Errorf("failed to initialize host subnet ip allocator: %w", err) diff --git a/go-controller/pkg/clustermanager/node/node_allocator.go b/go-controller/pkg/clustermanager/node/node_allocator.go index 8629098812..e4def50207 100644 --- a/go-controller/pkg/clustermanager/node/node_allocator.go +++ b/go-controller/pkg/clustermanager/node/node_allocator.go @@ -2,9 +2,13 @@ package node import ( "fmt" + udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" "net" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" listers "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/util/retry" @@ -32,8 +36,9 @@ import ( // Only for the default or layer3 networks. // - stores the network id in each node's annotation. type NodeAllocator struct { - kube kube.Interface - nodeLister listers.NodeLister + kube kube.InterfaceOVN + nodeLister listers.NodeLister + udnNodeLister v1.UDNNodeLister // idAllocator of IDs within the network idAllocator id.Allocator clusterSubnetAllocator SubnetAllocator @@ -48,10 +53,12 @@ type NodeAllocator struct { netInfo util.NetInfo } -func NewNodeAllocator(networkID int, netInfo util.NetInfo, nodeLister listers.NodeLister, kube kube.Interface, tunnelIDAllocator id.Allocator) *NodeAllocator { +func NewNodeAllocator(networkID int, netInfo util.NetInfo, nodeLister listers.NodeLister, udnNodeLister v1.UDNNodeLister, + kube kube.InterfaceOVN, tunnelIDAllocator id.Allocator) *NodeAllocator { na := &NodeAllocator{ kube: kube, nodeLister: nodeLister, + udnNodeLister: udnNodeLister, networkID: networkID, netInfo: netInfo, clusterSubnetAllocator: NewSubnetAllocator(), @@ -199,10 +206,85 @@ func (na *NodeAllocator) HandleAddUpdateNodeEvent(node *corev1.Node) error { return na.syncNodeNetworkAnnotations(node) } -// syncNodeNetworkAnnotations does 2 things -// - syncs the node's allocated subnets in the node subnet annotation -// - syncs the network id in the node network id annotation -func (na *NodeAllocator) syncNodeNetworkAnnotations(node *corev1.Node) error { +func (na *NodeAllocator) syncUDNNodeNetworkAnnotations(node *corev1.Node) error { + udnNodeName := fmt.Sprintf("%s-%d", node.Name, na.networkID) + udnNode, err := na.udnNodeLister.Get(udnNodeName) + if err != nil && !kerrors.IsNotFound(err) { + return err + } + if udnNode != nil { + // udnNode was already created, values are immutable + return nil + } + + spec := udnnodev1.UDNNodeSpec{} + + // udnNode is missing, lets create it + if na.hasJoinSubnetAllocation() { + var joinAddr udnnodev1.DualStackCIDRs + // Allocate the IP address(es) for the node Gateway router port connecting + // to the Join switch + nodeID := util.GetNodeID(node) + if nodeID == -1 { + // Don't consider this node as cluster-manager has not allocated node id yet. + return fmt.Errorf("failed to get node id for node - %s", node.Name) + } + + if config.IPv4Mode { + joinV4Addr, err := na.nodeGWRouterLRPIPv4Generator.GenerateIP(nodeID) + if err != nil { + return fmt.Errorf("failed to generate gateway router port IPv4 address for node %s : err - %w", node.Name, err) + } + joinAddr = append(joinAddr, udnnodev1.CIDR(joinV4Addr.String())) + } + + if config.IPv6Mode { + joinV6Addr, err := na.nodeGWRouterLRPIPv6Generator.GenerateIP(nodeID) + if err != nil { + return fmt.Errorf("failed to generate gateway router port IPv6 address for node %s : err - %w", node.Name, err) + } + joinAddr = append(joinAddr, udnnodev1.CIDR(joinV6Addr.String())) + } + + spec.JoinSubnets = joinAddr + } + + if na.hasNodeSubnetAllocation() { + // On return validExistingSubnets will contain any valid subnets that + // were already assigned to the node. allocatedSubnets will contain + // any newly allocated subnets required to ensure that the node has one subnet + // from each enabled IP family. + ipv4Mode, ipv6Mode := na.netInfo.IPMode() + _, allocatedNets, err := na.allocateNodeSubnets(na.clusterSubnetAllocator, node.Name, nil, ipv4Mode, ipv6Mode) + if err != nil { + return err + } + var nodeSubnets udnnodev1.DualStackCIDRs + for _, allocatedSubnet := range allocatedNets { + nodeSubnets = append(nodeSubnets, udnnodev1.CIDR(allocatedSubnet.String())) + } + spec.NodeSubnets = nodeSubnets + } + + x := &udnnodev1.UDNNode{ + ObjectMeta: metav1.ObjectMeta{ + Name: udnNodeName, + Labels: map[string]string{ + "networkName": na.netInfo.GetNetworkName(), + "nodeName": node.Name, + }, + }, + Spec: spec, + } + + y, err := na.kube.CreateUDNNode(x) + if y != nil { + klog.Infof("UDNNode created: %#v", *y) + } + return err +} + +func (na *NodeAllocator) syncDefaultNodeNetworkAnnotations(node *corev1.Node) error { networkName := na.netInfo.GetNetworkName() networkID, err := util.ParseNetworkIDAnnotation(node, networkName) @@ -318,6 +400,16 @@ func (na *NodeAllocator) syncNodeNetworkAnnotations(node *corev1.Node) error { return nil } +// syncNodeNetworkAnnotations does 2 things +// - syncs the node's allocated subnets in the node subnet annotation +// - syncs the network id in the node network id annotation +func (na *NodeAllocator) syncNodeNetworkAnnotations(node *corev1.Node) error { + if na.networkID == 0 { + return na.syncDefaultNodeNetworkAnnotations(node) + } + return na.syncUDNNodeNetworkAnnotations(node) +} + // HandleDeleteNode handles the delete node event func (na *NodeAllocator) HandleDeleteNode(node *corev1.Node) error { if na.hasHybridOverlayAllocation() { diff --git a/go-controller/pkg/crd/udnnode/v1/doc.go b/go-controller/pkg/crd/udnnode/v1/doc.go new file mode 100644 index 0000000000..5703f91c44 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/doc.go @@ -0,0 +1,4 @@ +// Package v1 contains API Schema definitions for the network v1 API group +// +k8s:deepcopy-gen=package +// +groupName=k8s.ovn.org +package v1 diff --git a/go-controller/pkg/crd/udnnode/v1/register.go b/go-controller/pkg/crd/udnnode/v1/register.go new file mode 100644 index 0000000000..1a32d55bec --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/register.go @@ -0,0 +1,33 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + GroupName = "k8s.ovn.org" + SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &UDNNode{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/go-controller/pkg/crd/udnnode/v1/types.go b/go-controller/pkg/crd/udnnode/v1/types.go new file mode 100644 index 0000000000..5f1fb60294 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/types.go @@ -0,0 +1,89 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:path=udnnodes,scope=Cluster +// +kubebuilder::singular=udnnode +// +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=".status.status" +// +kubebuilder:subresource:status +// UDNNode holds node specific information per network +type UDNNode struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UDNNodeSpec `json:"spec,omitempty"` + Status UDNNodeStatus `json:"status,omitempty"` +} + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// UDNNodeSpec defines the desired state of UDNNode +type UDNNodeSpec struct { + // NodeSubnets are used for the pod network across the cluster. + // + // Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. + // Given subnet is split into smaller subnets for every node. + // + // +optional + NodeSubnets DualStackCIDRs `json:"nodeSubnets,omitempty"` + + // JoinSubnets are used inside the OVN network topology. + // + // Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. + // This field is only allowed for "Primary" network. + // It is not recommended to set this field without explicit need and understanding of the OVN network topology. + // When omitted, the platform will choose a reasonable default which is subject to change over time. + // + // +optional + JoinSubnets DualStackCIDRs `json:"joinSubnets,omitempty"` +} + +// UDNNodeStatus defines the observed state of UDNNode +type UDNNodeStatus struct { + // A concise indication of whether the EgressQoS resource is applied with success. + // +optional + Status string `json:"status,omitempty"` + + // An array of condition objects indicating details about status of EgressQoS object. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// + --- +// + TODO: Add the following validations when available (kube v1.31). +// + kubebuilder:validation:XValidation:rule="isCIDR(self)", message="CIDR is invalid" +type CIDR string + +// +kubebuilder:validation:MinItems=1 +// +kubebuilder:validation:MaxItems=2 +// + --- +// + TODO: Add the following validations when available (kube v1.31). +// + kubebuilder:validation:XValidation:rule="size(self) != 2 || isCIDR(self[0]) && isCIDR(self[1]) && cidr(self[0]).ip().family() != cidr(self[1]).ip().family()", message="When 2 CIDRs are set, they must be from different IP families" +type DualStackCIDRs []CIDR diff --git a/go-controller/pkg/factory/factory.go b/go-controller/pkg/factory/factory.go index c6eaaf4c9f..099547ae3c 100644 --- a/go-controller/pkg/factory/factory.go +++ b/go-controller/pkg/factory/factory.go @@ -70,6 +70,9 @@ import ( ipamclaimsinformer "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1" ipamclaimslister "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + userdefinednodeinformerfactory "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions" + userdefinednodeinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1" userdefinednetworkapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" userdefinednetworkscheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned/scheme" userdefinednetworkapiinformerfactory "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/informers/externalversions" @@ -114,6 +117,7 @@ type WatchFactory struct { ipamClaimsFactory ipamclaimsfactory.SharedInformerFactory nadFactory nadinformerfactory.SharedInformerFactory udnFactory userdefinednetworkapiinformerfactory.SharedInformerFactory + udnNodeFactory userdefinednodeinformerfactory.SharedInformerFactory informers map[reflect.Type]*informer stopChan chan struct{} @@ -187,6 +191,7 @@ var ( IPAMClaimsType reflect.Type = reflect.TypeOf(&ipamclaimsapi.IPAMClaim{}) UserDefinedNetworkType reflect.Type = reflect.TypeOf(&userdefinednetworkapi.UserDefinedNetwork{}) ClusterUserDefinedNetworkType reflect.Type = reflect.TypeOf(&userdefinednetworkapi.ClusterUserDefinedNetwork{}) + UserDefinedNodeType reflect.Type = reflect.TypeOf(&userdefinednodeapi.UDNNode{}) // Resource types used in ovnk node NamespaceExGwType reflect.Type = reflect.TypeOf(&namespaceExGw{}) @@ -878,11 +883,18 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset return nil, err } + wf.udnNodeFactory = userdefinednodeinformerfactory.NewSharedInformerFactory(ovnClientset.UserDefinedNodeClient, resyncInterval) + wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, wf.udnNodeFactory.K8s().V1().UDNNodes().Informer()) + if err != nil { + return nil, err + } + // make sure namespace informer cache is initialized and synced on Start(). wf.iFactory.Core().V1().Namespaces().Informer() // make sure pod informer cache is initialized and synced when on Start(). wf.iFactory.Core().V1().Pods().Informer() + } return wf, nil @@ -1558,6 +1570,10 @@ func (wf *WatchFactory) ClusterUserDefinedNetworkInformer() userdefinednetworkin return wf.udnFactory.K8s().V1().ClusterUserDefinedNetworks() } +func (wf *WatchFactory) UserDefinedNodeInformer() userdefinednodeinformer.UDNNodeInformer { + return wf.udnNodeFactory.K8s().V1().UDNNodes() +} + func (wf *WatchFactory) DNSNameResolverInformer() ocpnetworkinformerv1alpha1.DNSNameResolverInformer { return wf.dnsFactory.Network().V1alpha1().DNSNameResolvers() } diff --git a/go-controller/pkg/kube/kube.go b/go-controller/pkg/kube/kube.go index a287f058c7..7a75bf7114 100644 --- a/go-controller/pkg/kube/kube.go +++ b/go-controller/pkg/kube/kube.go @@ -14,6 +14,8 @@ import ( egressipclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1/apis/clientset/versioned" egressqosclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressqos/v1/apis/clientset/versioned" egressserviceclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressservice/v1/apis/clientset/versioned" + udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + udnnodeclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" kapi "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -42,6 +44,7 @@ type InterfaceOVN interface { DeleteCloudPrivateIPConfig(name string) error UpdateEgressServiceStatus(namespace, name, host string) error UpdateIPAMClaimIPs(updatedIPAMClaim *ipamclaimsapi.IPAMClaim) error + CreateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) } // Interface represents the exported methods for dealing with getting/setting @@ -84,6 +87,7 @@ type KubeOVN struct { APBRouteClient adminpolicybasedrouteclientset.Interface EgressQoSClient egressqosclientset.Interface IPAMClaimsClient ipamclaimssclientset.Interface + UDNNodeClient udnnodeclientset.Interface } // SetAnnotationsOnPod takes the pod object and map of key/value string pairs to set as annotations @@ -463,3 +467,7 @@ func (k *KubeOVN) UpdateIPAMClaimIPs(updatedIPAMClaim *ipamclaimsapi.IPAMClaim) _, err := k.IPAMClaimsClient.K8sV1alpha1().IPAMClaims(updatedIPAMClaim.Namespace).UpdateStatus(context.TODO(), updatedIPAMClaim, metav1.UpdateOptions{}) return err } + +func (k *KubeOVN) CreateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) { + return k.UDNNodeClient.K8sV1().UDNNodes().Create(context.TODO(), udnNode, metav1.CreateOptions{}) +} diff --git a/go-controller/pkg/util/kube.go b/go-controller/pkg/util/kube.go index f82dbd6ef9..ae1e75fd3a 100644 --- a/go-controller/pkg/util/kube.go +++ b/go-controller/pkg/util/kube.go @@ -46,6 +46,7 @@ import ( egressipclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1/apis/clientset/versioned" egressqosclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressqos/v1/apis/clientset/versioned" egressserviceclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressservice/v1/apis/clientset/versioned" + userdefinednodeclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" userdefinednetworkclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned" anpclientset "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned" ) @@ -122,6 +123,7 @@ type OVNClusterManagerClientset struct { IPAMClaimsClient ipamclaimssclientset.Interface OCPNetworkClient ocpnetworkclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } const ( From 59a7d8195c4e50a50c0fb672a107736c650329ef Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 18 Nov 2024 17:19:40 -0500 Subject: [PATCH 03/17] Continue updating l3 secondary controller Next need to: - add mgmt port mac address - update l2 secondary - update node tracker Signed-off-by: Tim Rozet --- go-controller/pkg/crd/udnnode/v1/types.go | 13 + go-controller/pkg/factory/factory.go | 14 +- .../pkg/ovn/base_network_controller.go | 30 +- .../ovn/base_network_controller_secondary.go | 8 +- go-controller/pkg/ovn/master.go | 94 +++-- go-controller/pkg/ovn/ovn.go | 16 +- .../secondary_layer3_network_controller.go | 389 ++++++++++++------ .../ovn/zone_interconnect/zone_ic_handler.go | 104 ++--- go-controller/pkg/util/kube.go | 9 + go-controller/pkg/util/node_annotations.go | 17 + go-controller/pkg/util/subnet_annotations.go | 13 + 11 files changed, 450 insertions(+), 257 deletions(-) diff --git a/go-controller/pkg/crd/udnnode/v1/types.go b/go-controller/pkg/crd/udnnode/v1/types.go index 5f1fb60294..bfbfa0a8d6 100644 --- a/go-controller/pkg/crd/udnnode/v1/types.go +++ b/go-controller/pkg/crd/udnnode/v1/types.go @@ -59,6 +59,8 @@ type UDNNodeSpec struct { // // +optional JoinSubnets DualStackCIDRs `json:"joinSubnets,omitempty"` + + ManagementPortMACAddress string `json:"managementPortMACAddress,omitempty"` } // UDNNodeStatus defines the observed state of UDNNode @@ -87,3 +89,14 @@ type CIDR string // + TODO: Add the following validations when available (kube v1.31). // + kubebuilder:validation:XValidation:rule="size(self) != 2 || isCIDR(self[0]) && isCIDR(self[1]) && cidr(self[0]).ip().family() != cidr(self[1]).ip().family()", message="When 2 CIDRs are set, they must be from different IP families" type DualStackCIDRs []CIDR + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +resource:path=udnnode +// UDNNodeList is the list of UDNNodes. +type UDNNodeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + // List of EgressIP. + Items []UDNNode `json:"items"` +} diff --git a/go-controller/pkg/factory/factory.go b/go-controller/pkg/factory/factory.go index 099547ae3c..fffab22b77 100644 --- a/go-controller/pkg/factory/factory.go +++ b/go-controller/pkg/factory/factory.go @@ -884,10 +884,22 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset } wf.udnNodeFactory = userdefinednodeinformerfactory.NewSharedInformerFactory(ovnClientset.UserDefinedNodeClient, resyncInterval) - wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, wf.udnNodeFactory.K8s().V1().UDNNodes().Informer()) + udnNodeInformer := wf.udnNodeFactory.K8s().V1().UDNNodes().Informer() + + indexers := cache.Indexers{ + "byNodeAndNetwork": func(obj interface{}) ([]string, error) { + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) + if !ok { + return nil, fmt.Errorf("unexpected type") + } + return []string{udnNode.Labels["nodeName"] + "-" + udnNode.Labels["networkName"]}, nil + }, + } + err := udnNodeInformer.AddIndexers(indexers) if err != nil { return nil, err } + wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, wf.udnNodeFactory.K8s().V1().UDNNodes().Informer()) // make sure namespace informer cache is initialized and synced on Start(). wf.iFactory.Core().V1().Namespaces().Informer() diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index a1a263eeae..ed49e91bae 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -2,6 +2,7 @@ package ovn import ( "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" "reflect" "sync" @@ -97,6 +98,8 @@ type BaseNetworkController struct { retryMultiNetworkPolicies *ovnretry.RetryFramework // retry framework for IPAMClaims retryIPAMClaims *ovnretry.RetryFramework + // retry framework for UDN Nodes + retryUDNNodes *ovnretry.RetryFramework // pod events factory handler podHandler *factory.Handler @@ -625,12 +628,7 @@ func (bnc *BaseNetworkController) deleteNamespaceLocked(ns string) (*namespaceIn return nsInfo, nil } -func (bnc *BaseNetworkController) syncNodeManagementPort(node *kapi.Node, switchName, routerName string, hostSubnets []*net.IPNet) ([]net.IP, error) { - macAddress, err := util.ParseNodeManagementPortMACAddresses(node, bnc.GetNetworkName()) - if err != nil { - return nil, err - } - +func (bnc *BaseNetworkController) syncNodeManagementPort(macAddress net.HardwareAddr, nodeName, switchName, routerName string, hostSubnets []*net.IPNet) ([]net.IP, error) { var v4Subnet *net.IPNet addresses := macAddress.String() mgmtPortIPs := []net.IP{} @@ -671,7 +669,7 @@ func (bnc *BaseNetworkController) syncNodeManagementPort(node *kapi.Node, switch // Create this node's management logical port on the node switch logicalSwitchPort := nbdb.LogicalSwitchPort{ - Name: bnc.GetNetworkScopedK8sMgmtIntfName(node.Name), + Name: bnc.GetNetworkScopedK8sMgmtIntfName(nodeName), Addresses: []string{addresses}, } sw := nbdb.LogicalSwitch{Name: switchName} @@ -693,7 +691,8 @@ func (bnc *BaseNetworkController) syncNodeManagementPort(node *kapi.Node, switch } if v4Subnet != nil { - if err := libovsdbutil.UpdateNodeSwitchExcludeIPs(bnc.nbClient, bnc.GetNetworkScopedK8sMgmtIntfName(node.Name), bnc.GetNetworkScopedSwitchName(node.Name), node.Name, v4Subnet); err != nil { + if err := libovsdbutil.UpdateNodeSwitchExcludeIPs(bnc.nbClient, bnc.GetNetworkScopedK8sMgmtIntfName(node.Name), + bnc.GetNetworkScopedSwitchName(nodeName), nodeName, v4Subnet); err != nil { return nil, err } } @@ -714,6 +713,21 @@ func (bnc *BaseNetworkController) WatchNodes() error { return err } +func (bnc *BaseNetworkController) recordUDNNodeErrorEvent(udnNode *userdefinednodeapi.UDNNode, nodeErr error) { + if bnc.IsSecondary() { + // TBD, no op for secondary network for now + return + } + nodeRef, err := ref.GetReference(scheme.Scheme, udnNode) + if err != nil { + klog.Errorf("Couldn't get a reference to node %s to post an event: %v", udnNode.Name, err) + return + } + + klog.V(5).Infof("Posting %s event for Node %s: %v", kapi.EventTypeWarning, udnNode.Name, nodeErr) + bnc.recorder.Eventf(nodeRef, kapi.EventTypeWarning, "ErrorReconcilingNode", nodeErr.Error()) +} + func (bnc *BaseNetworkController) recordNodeErrorEvent(node *kapi.Node, nodeErr error) { if bnc.IsSecondary() { // TBD, no op for secondary network for now diff --git a/go-controller/pkg/ovn/base_network_controller_secondary.go b/go-controller/pkg/ovn/base_network_controller_secondary.go index b2d7f2ea14..10942ec642 100644 --- a/go-controller/pkg/ovn/base_network_controller_secondary.go +++ b/go-controller/pkg/ovn/base_network_controller_secondary.go @@ -805,8 +805,8 @@ func (oc *BaseSecondaryNetworkController) getNetworkID() (int, error) { // buildUDNEgressSNAT is used to build the conditional SNAT required on L3 and L2 UDNs to // steer traffic correctly via mp0 when leaving OVN to the host -func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets []*net.IPNet, outputPort string, - node *kapi.Node) ([]*nbdb.NAT, error) { +func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets []*net.IPNet, outputPort, nodeName string, + macAddr net.HardwareAddr) ([]*nbdb.NAT, error) { if len(localPodSubnets) == 0 { return nil, nil // nothing to do } @@ -817,10 +817,10 @@ func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets [ if err != nil { return nil, fmt.Errorf("failed to get networkID for network %q: %v", bsnc.GetNetworkName(), err) } - dstMac, err := util.ParseNodeManagementPortMACAddresses(node, bsnc.GetNetworkName()) + dstMac, err := util.ParseNodeManagementPortMACAddresses(macAddr, bsnc.GetNetworkName()) if err != nil { return nil, fmt.Errorf("failed to parse mac address annotation for network %q on node %q, err: %w", - bsnc.GetNetworkName(), node.Name, err) + bsnc.GetNetworkName(), nodeName, err) } extIDs := map[string]string{ types.NetworkExternalID: bsnc.GetNetworkName(), diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index 625e12c38b..40026326c0 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -130,7 +130,11 @@ func (oc *DefaultNetworkController) newClusterRouter() (*nbdb.LogicalRouter, err } func (oc *DefaultNetworkController) syncNodeManagementPortDefault(node *kapi.Node, switchName string, hostSubnets []*net.IPNet) error { - mgmtPortIPs, err := oc.syncNodeManagementPort(node, switchName, oc.GetNetworkScopedClusterRouterName(), hostSubnets) + macAddress, err := util.ParseNodeManagementPortMACAddresses(node, oc.GetNetworkName()) + if err != nil { + return err + } + mgmtPortIPs, err := oc.syncNodeManagementPort(macAddress, node.Name, switchName, oc.GetNetworkScopedClusterRouterName(), hostSubnets) if err == nil { return oc.setupUDNACLs(mgmtPortIPs) } @@ -142,25 +146,13 @@ func (oc *DefaultNetworkController) syncDefaultGatewayLogicalNetwork( l3GatewayConfig *util.L3GatewayConfig, hostSubnets []*net.IPNet, hostAddrs []string, + gwLRPIPs []*net.IPNet, ) error { var clusterSubnets []*net.IPNet for _, clusterSubnet := range config.Default.ClusterSubnets { clusterSubnets = append(clusterSubnets, clusterSubnet.CIDR) } - gwLRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, oc.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // FIXME(tssurya): This is present for backwards compatibility - // Remove me a few months from now - var err1 error - gwLRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) - if err1 != nil { - return fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) - } - } - } - externalIPs := make([]net.IP, len(l3GatewayConfig.IPAddresses)) for i, ip := range l3GatewayConfig.IPAddresses { externalIPs[i] = ip.IP @@ -595,7 +587,7 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *kapi.Node, nSy } } - // If we succcessfully discovered the host subnets then add the management port. + // If we successfully discovered the host subnets then add the management port. if hostSubnets != nil { if err = oc.syncNodeManagementPortDefault(node, oc.GetNetworkScopedSwitchName(node.Name), hostSubnets); err != nil { errs = append(errs, err) @@ -628,13 +620,33 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *kapi.Node, nSy errs = append(errs, fmt.Errorf("failed to set hybrid overlay annotations for node %s: %v", node.Name, err)) } - if nSyncs.syncGw { - err := oc.syncNodeGateway(node, nil) + var gwLRPIPs []*net.IPNet + if nSyncs.syncGw || (nSyncs.syncZoneIC && config.OVNKubernetesFeature.EnableInterconnect) { + gwLRPIPs, err = util.ParseNodeGatewayRouterJoinAddrs(node, oc.GetNetworkName()) if err != nil { - errs = append(errs, err) - oc.gatewaysFailed.Store(node.Name, true) + if util.IsAnnotationNotSetError(err) { + // FIXME(tssurya): This is present for backwards compatibility + // Remove me a few months from now + var err1 error + gwLRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) + if err1 != nil { + return fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) + } + } + } + } + + if nSyncs.syncGw { + if gwLRPIPs != nil { + err := oc.syncNodeGateway(node, hostSubnets, gwLRPIPs) + if err != nil { + errs = append(errs, err) + oc.gatewaysFailed.Store(node.Name, true) + } else { + oc.gatewaysFailed.Delete(node.Name) + } } else { - oc.gatewaysFailed.Delete(node.Name) + oc.gatewaysFailed.Store(node.Name, true) } } @@ -655,13 +667,21 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *kapi.Node, nSy errs = append(errs, err) oc.syncZoneICFailed.Store(node.Name, true) } else { - // Call zone IC handler's AddLocalZoneNode function to create - // interconnect resources in the OVN Northbound db for this local zone node. - if err := oc.zoneICHandler.AddLocalZoneNode(node); err != nil { - errs = append(errs, err) - oc.syncZoneICFailed.Store(node.Name, true) + if hostSubnets == nil { + hostSubnets, err = util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) + if err != nil { + errs = append(errs, err) + oc.syncZoneICFailed.Store(node.Name, true) + } } else { - oc.syncZoneICFailed.Delete(node.Name) + // Call zone IC handler's AddLocalZoneNode function to create + // interconnect resources in the OVN Northbound db for this local zone node. + if err := oc.zoneICHandler.AddLocalZoneNode(hostSubnets, gwLRPIPs, node); err != nil { + errs = append(errs, err) + oc.syncZoneICFailed.Store(node.Name, true) + } else { + oc.syncZoneICFailed.Delete(node.Name) + } } } } @@ -699,10 +719,28 @@ func (oc *DefaultNetworkController) addUpdateRemoteNodeEvent(node *kapi.Node, sy return err } + hostSubnets, err := util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) + if err != nil { + return fmt.Errorf("failed to get host subnets for node: %s: %w", node.Name, err) + } + + gwLRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, oc.GetNetworkName()) + if err != nil { + if util.IsAnnotationNotSetError(err) { + // FIXME(tssurya): This is present for backwards compatibility + // Remove me a few months from now + var err1 error + gwLRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) + if err1 != nil { + return fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) + } + } + } + // Call zone IC handler's AddRemoteZoneNode function to create // interconnect resources in the OVN NBDB for this remote zone node. // Also, create the remote port binding in SBDB - if err = oc.zoneICHandler.AddRemoteZoneNode(node); err != nil { + if err = oc.zoneICHandler.AddRemoteZoneNode(hostSubnets, gwLRPIPs, node); err != nil { err = fmt.Errorf("adding or updating remote node IC resources %s failed, err - %w", node.Name, err) oc.syncZoneICFailed.Store(node.Name, true) } else { @@ -736,7 +774,7 @@ func (oc *DefaultNetworkController) deleteOVNNodeEvent(node *kapi.Node) error { } if config.OVNKubernetesFeature.EnableInterconnect { - if err := oc.zoneICHandler.DeleteNode(node); err != nil { + if err := oc.zoneICHandler.DeleteNode(node.Name); err != nil { return err } if !oc.isLocalZoneNode(node) { diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index d70ac3e89d..2f69642398 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" "reflect" "sync" @@ -371,7 +372,7 @@ func (oc *DefaultNetworkController) WatchEgressIPPods() error { } // syncNodeGateway ensures a node's gateway router is configured -func (oc *DefaultNetworkController) syncNodeGateway(node *kapi.Node, hostSubnets []*net.IPNet) error { +func (oc *DefaultNetworkController) syncNodeGateway(node *kapi.Node, hostSubnets, gwLRPIPs []*net.IPNet) error { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return err @@ -396,7 +397,7 @@ func (oc *DefaultNetworkController) syncNodeGateway(node *kapi.Node, hostSubnets return fmt.Errorf("failed to get host CIDRs for node: %s: %v", node.Name, err) } } - if err := oc.syncDefaultGatewayLogicalNetwork(node, l3GatewayConfig, hostSubnets, hostAddrs); err != nil { + if err := oc.syncDefaultGatewayLogicalNetwork(node, l3GatewayConfig, hostSubnets, hostAddrs, gwLRPIPs); err != nil { return fmt.Errorf("error creating gateway for node %s: %v", node.Name, err) } } @@ -424,12 +425,23 @@ func macAddressChanged(oldNode, node *kapi.Node, netName string) bool { return !bytes.Equal(oldMacAddress, macAddress) } +func udnNodeMACAddressChanged(oldNode, node *userdefinednodeapi.UDNNode) bool { + return oldNode.Spec.ManagementPortMACAddress != node.Spec.ManagementPortMACAddress +} + func nodeSubnetChanged(oldNode, node *kapi.Node, netName string) bool { oldSubnets, _ := util.ParseNodeHostSubnetAnnotation(oldNode, netName) newSubnets, _ := util.ParseNodeHostSubnetAnnotation(node, netName) return !reflect.DeepEqual(oldSubnets, newSubnets) } +func udnNodeSubnetChanged(oldNode, node *userdefinednodeapi.UDNNode) bool { + if !reflect.DeepEqual(oldNode.Spec.NodeSubnets, node.Spec.NodeSubnets) { + return true + } + return false +} + func joinCIDRChanged(oldNode, node *kapi.Node, netName string) bool { oldSubnets, _ := util.ParseNodeGatewayRouterJoinNetwork(oldNode, netName) newSubnets, _ := util.ParseNodeGatewayRouterJoinNetwork(node, netName) diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 46589144e7..72ec4937e7 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -3,6 +3,7 @@ package ovn import ( "context" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" "reflect" "sync" @@ -97,12 +98,22 @@ func (h *secondaryLayer3NetworkControllerEventHandler) IsResourceScheduled(obj i // Given an object to add and a boolean specifying if the function was executed from iterateRetryResources func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface{}, fromRetryLoop bool) error { switch h.objType { - case factory.NodeType: - node, ok := obj.(*kapi.Node) + case factory.UserDefinedNodeType: + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { return fmt.Errorf("could not cast %T object to *kapi.Node", obj) } + nodeName := udnNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) + } + + node, err := h.watchFactory.GetNode(nodeName) + if err != nil { + return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, udnNode.Name) + } + if h.oc.isLocalZoneNode(node) { var nodeParams *nodeSyncs if fromRetryLoop { @@ -127,13 +138,13 @@ func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface syncGw: true, } } - if err := h.oc.addUpdateLocalNodeEvent(node, nodeParams); err != nil { - klog.Errorf("Node add failed for %s, will try again later: %v", + if err := h.oc.addUpdateLocalNodeEvent(udnNode, node, nodeParams); err != nil { + klog.Errorf("UDN Node add failed for %s, will try again later: %v", node.Name, err) return err } } else { - if err := h.oc.addUpdateRemoteNodeEvent(node, config.OVNKubernetesFeature.EnableInterconnect); err != nil { + if err := h.oc.addUpdateRemoteNodeEvent(udnNode, node, config.OVNKubernetesFeature.EnableInterconnect); err != nil { return err } } @@ -158,58 +169,121 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne if !ok { return fmt.Errorf("could not cast oldObj of type %T to *kapi.Node", oldObj) } + // zone change newNodeIsLocalZoneNode := h.oc.isLocalZoneNode(newNode) zoneClusterChanged := h.oc.nodeZoneClusterChanged(oldNode, newNode, newNodeIsLocalZoneNode, h.oc.NetInfo.GetNetworkName()) - nodeSubnetChanged := nodeSubnetChanged(oldNode, newNode, h.oc.NetInfo.GetNetworkName()) + // transit switch subnet change, node id, chassis id, primary if addr, l3 gateway config change determine + // if we need to trigger UDN node type update + needsResync := false + + if newNodeIsLocalZoneNode && !h.oc.isLocalZoneNode(oldNode) { + // this is a remote -> local transition, need full sync + needsResync = true + klog.Infof("Node %s moved from the remote zone %s to local zone %s.", + newNode.Name, util.GetNodeZone(oldNode), util.GetNodeZone(newNode)) + if config.OVNKubernetesFeature.EnableInterconnect { + h.oc.syncZoneICFailed.Store(newNode.Name, true) + } + h.oc.nodeClusterRouterPortFailed.Store(newNode.Name, true) + h.oc.gatewaysFailed.Store(newNode.Name, true) + h.oc.addNodeFailed.Store(newNode.Name, true) + h.oc.mgmtPortFailed.Store(newNode.Name, true) + } + + if !newNodeIsLocalZoneNode && h.oc.isLocalZoneNode(oldNode) { + // this is local -> remote transition + needsResync = true + klog.Infof("Node %s in remote zone %s needs interconnect zone sync up. Zone cluster changed: %v", + newNode.Name, util.GetNodeZone(newNode), zoneClusterChanged) + if config.OVNKubernetesFeature.EnableInterconnect { + h.oc.syncZoneICFailed.Store(newNode.Name, true) + } + } + + // load necessary failure flags into the cache to force a resync + // this could race with the UDN Node type handler, should look at this later + // zoneClusterChanged checks transit switch and node ID + if zoneClusterChanged && config.OVNKubernetesFeature.EnableInterconnect { + // need to sync IC again + h.oc.syncZoneICFailed.Store(newNode.Name, true) + needsResync = true + } + + if nodeChassisChanged(oldNode, newNode) { + h.oc.nodeClusterRouterPortFailed.Store(newNode.Name, true) + needsResync = true + } + + // TODO(trozet) check if hostCIDRs really matters for secondary + if primaryAddrChanged(oldNode, newNode) || gatewayChanged(oldNode, newNode) || hostCIDRsChanged(oldNode, newNode) || + nodeGatewayMTUSupportChanged(oldNode, newNode) { + h.oc.gatewaysFailed.Store(newNode.Name, true) + needsResync = true + } + + if needsResync { + networkID, err := h.oc.getNetworkID() + if err != nil { + return err + } + // reset + if err := h.oc.retryUDNNodes.AddRetryObjWithAddNoBackoff(fmt.Sprintf("%d-%s", networkID, newNode.Name)); err != nil { + return err + } + } + return nil + + case factory.UserDefinedNodeType: + newUDNNode, ok := newObj.(*userdefinednodeapi.UDNNode) + if !ok { + return fmt.Errorf("could not cast newObj of type %T to *userdefinednodeapi.UDNNode", newObj) + } + oldUDNNode, ok := oldObj.(*userdefinednodeapi.UDNNode) + if !ok { + return fmt.Errorf("could not cast oldObj of type %T to *userdefinednodeapi.UDNNode", oldObj) + } + + nodeName := newUDNNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", newUDNNode.Name) + } + + node, err := h.watchFactory.GetNode(nodeName) + if err != nil { + return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, newUDNNode.Name) + } + + newNodeIsLocalZoneNode := h.oc.isLocalZoneNode(node) + // can node subnet change for UDN? + nodeSubnetChanged := udnNodeSubnetChanged(oldUDNNode, newUDNNode) if newNodeIsLocalZoneNode { var nodeSyncsParam *nodeSyncs - if h.oc.isLocalZoneNode(oldNode) { - // determine what actually changed in this update - _, nodeSync := h.oc.addNodeFailed.Load(newNode.Name) - _, failed := h.oc.nodeClusterRouterPortFailed.Load(newNode.Name) - clusterRtrSync := failed || nodeChassisChanged(oldNode, newNode) || nodeSubnetChanged - _, failed = h.oc.mgmtPortFailed.Load(newNode.Name) - syncMgmtPort := failed || macAddressChanged(oldNode, newNode, h.oc.GetNetworkName()) || nodeSubnetChanged - _, syncZoneIC := h.oc.syncZoneICFailed.Load(newNode.Name) - syncZoneIC = syncZoneIC || zoneClusterChanged - _, failed = h.oc.gatewaysFailed.Load(newNode.Name) - syncGw := failed || - gatewayChanged(oldNode, newNode) || - nodeSubnetChanged || - hostCIDRsChanged(oldNode, newNode) || - nodeGatewayMTUSupportChanged(oldNode, newNode) - nodeSyncsParam = &nodeSyncs{ - syncNode: nodeSync, - syncClusterRouterPort: clusterRtrSync, - syncMgmtPort: syncMgmtPort, - syncZoneIC: syncZoneIC, - syncGw: syncGw, - } - } else { - klog.Infof("Node %s moved from the remote zone %s to local zone %s.", - newNode.Name, util.GetNodeZone(oldNode), util.GetNodeZone(newNode)) - // The node is now a local zone node. Trigger a full node sync. - nodeSyncsParam = &nodeSyncs{ - syncNode: true, - syncClusterRouterPort: true, - syncMgmtPort: true, - syncZoneIC: config.OVNKubernetesFeature.EnableInterconnect, - syncGw: true, - } + + // determine what actually changed in this update + _, nodeSync := h.oc.addNodeFailed.Load(node.Name) + _, failed := h.oc.nodeClusterRouterPortFailed.Load(node.Name) + clusterRtrSync := failed || nodeSubnetChanged + _, failed = h.oc.mgmtPortFailed.Load(node.Name) + syncMgmtPort := failed || udnNodeMACAddressChanged(oldUDNNode, newUDNNode) || nodeSubnetChanged + _, syncZoneIC := h.oc.syncZoneICFailed.Load(node.Name) + _, failed = h.oc.gatewaysFailed.Load(node.Name) + syncGw := failed || nodeSubnetChanged + nodeSyncsParam = &nodeSyncs{ + syncNode: nodeSync, + syncClusterRouterPort: clusterRtrSync, + syncMgmtPort: syncMgmtPort, + syncZoneIC: syncZoneIC, + syncGw: syncGw, } - return h.oc.addUpdateLocalNodeEvent(newNode, nodeSyncsParam) + return h.oc.addUpdateLocalNodeEvent(newUDNNode, node, nodeSyncsParam) } else { - _, syncZoneIC := h.oc.syncZoneICFailed.Load(newNode.Name) + _, syncZoneIC := h.oc.syncZoneICFailed.Load(node.Name) // Check if the node moved from local zone to remote zone and if so syncZoneIC should be set to true. // Also check if node subnet changed, so static routes are properly set - syncZoneIC = syncZoneIC || h.oc.isLocalZoneNode(oldNode) || nodeSubnetChanged || zoneClusterChanged - if syncZoneIC { - klog.Infof("Node %s in remote zone %s needs interconnect zone sync up. Zone cluster changed: %v", - newNode.Name, util.GetNodeZone(newNode), zoneClusterChanged) - } - return h.oc.addUpdateRemoteNodeEvent(newNode, syncZoneIC) + syncZoneIC = syncZoneIC || nodeSubnetChanged + return h.oc.addUpdateRemoteNodeEvent(newUDNNode, node, syncZoneIC) } default: return h.oc.UpdateSecondaryNetworkResourceCommon(h.objType, oldObj, newObj, inRetryCache) @@ -221,12 +295,16 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne // used for now for pods and network policies. func (h *secondaryLayer3NetworkControllerEventHandler) DeleteResource(obj, cachedObj interface{}) error { switch h.objType { - case factory.NodeType: - node, ok := obj.(*kapi.Node) + case factory.UserDefinedNodeType: + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { return fmt.Errorf("could not cast obj of type %T to *knet.Node", obj) } - return h.oc.deleteNodeEvent(node) + nodeName := udnNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) + } + return h.oc.deleteNodeEvent(nodeName) default: return h.oc.DeleteSecondaryNetworkResourceCommon(h.objType, obj, cachedObj) @@ -402,6 +480,8 @@ func (oc *SecondaryLayer3NetworkController) initRetryFramework() { oc.retryNamespaces = oc.newRetryFramework(factory.NamespaceType) oc.retryMultiNetworkPolicies = oc.newRetryFramework(factory.MultiNetworkPolicyType) } + + oc.retryUDNNodes = oc.newRetryFramework(factory.UserDefinedNodeType) } // newRetryFramework builds and returns a retry framework for the input resource type; @@ -630,14 +710,12 @@ func (oc *SecondaryLayer3NetworkController) Init(ctx context.Context) error { return nil } -func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *kapi.Node, nSyncs *nodeSyncs) error { +func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(udnNode *userdefinednodeapi.UDNNode, node *kapi.Node, nSyncs *nodeSyncs) error { var hostSubnets []*net.IPNet var errs []error var err error - _, _ = oc.localZoneNodes.LoadOrStore(node.Name, true) - - if noHostSubnet := util.NoHostSubnet(node); noHostSubnet { + if noHostSubnet := util.NoHostSubnetUDNNode(udnNode); noHostSubnet { err := oc.lsManager.AddNoHostSubnetSwitch(oc.GetNetworkScopedName(node.Name)) if err != nil { return fmt.Errorf("nodeAdd: error adding noHost subnet for switch %s: %w", oc.GetNetworkScopedName(node.Name), err) @@ -645,22 +723,30 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *kapi.N return nil } - klog.Infof("Adding or Updating Node %q for network %s", node.Name, oc.GetNetworkName()) + hostSubnets, err = util.ParseNodeUDNHostSubnet(udnNode) + if err != nil || len(hostSubnets) < 1 { + return fmt.Errorf("subnets in the node %q for the layer3 secondary network %s is missing : %w", node.Name, oc.GetNetworkName(), err) + } + + _, _ = oc.localZoneNodes.LoadOrStore(udnNode.Name, true) + + klog.Infof("Adding or Updating UDN Node %q for network %s", udnNode.Name, oc.GetNetworkName()) if nSyncs.syncNode { - if hostSubnets, err = oc.addNode(node); err != nil { + if err = oc.addNode(udnNode, hostSubnets); err != nil { oc.addNodeFailed.Store(node.Name, true) oc.nodeClusterRouterPortFailed.Store(node.Name, true) oc.mgmtPortFailed.Store(node.Name, true) oc.syncZoneICFailed.Store(node.Name, true) oc.gatewaysFailed.Store(node.Name, true) - err = fmt.Errorf("nodeAdd: error adding node %q for network %s: %w", node.Name, oc.GetNetworkName(), err) - oc.recordNodeErrorEvent(node, err) + err = fmt.Errorf("nodeAdd: error adding node %q for network %s: %w", udnNode.Name, oc.GetNetworkName(), err) + oc.recordUDNNodeErrorEvent(udnNode, err) return err } oc.addNodeFailed.Delete(node.Name) } if nSyncs.syncClusterRouterPort { + // will not use any node attributes besides chassis-id if err = oc.syncNodeClusterRouterPort(node, hostSubnets); err != nil { errs = append(errs, err) oc.nodeClusterRouterPortFailed.Store(node.Name, true) @@ -671,57 +757,71 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *kapi.N if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { if nSyncs.syncMgmtPort { - hostSubnets, err := util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) + mac, err := net.ParseMAC(udnNode.Spec.ManagementPortMACAddress) + if err != nil { + return fmt.Errorf("failed to parse MAC for network %q on node %q, mac string: %q", oc.GetNetworkName(), node.Name, udnNode.Spec.ManagementPortMACAddress) + } + _, err = oc.syncNodeManagementPort(mac, node.Name, oc.GetNetworkScopedSwitchName(node.Name), oc.GetNetworkScopedClusterRouterName(), hostSubnets) if err != nil { errs = append(errs, err) oc.mgmtPortFailed.Store(node.Name, true) } else { - _, err = oc.syncNodeManagementPort(node, oc.GetNetworkScopedSwitchName(node.Name), oc.GetNetworkScopedClusterRouterName(), hostSubnets) - if err != nil { - errs = append(errs, err) - oc.mgmtPortFailed.Store(node.Name, true) - } else { - oc.mgmtPortFailed.Delete(node.Name) - } + oc.mgmtPortFailed.Delete(node.Name) } } } + syncSuccessful := false // ensure pods that already exist on this node have their logical ports created if nSyncs.syncNode { // do this only if it is a new node add errors := oc.addAllPodsOnNode(node.Name) - errs = append(errs, errors...) + if len(errors) > 0 { + errs = append(errs, errors...) + } else { + syncSuccessful = true + } + } + var gwLRPIPs []*net.IPNet if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { if nSyncs.syncGw { gwManager := oc.gatewayManagerForNode(node.Name) oc.gatewayManagers.Store(node.Name, gwManager) - gwConfig, err := oc.nodeGatewayConfig(node) + gwLRPIPs, err = util.ParseNodeUDNGatewayRouterJoinAddrs(udnNode) if err != nil { - errs = append(errs, fmt.Errorf("failed to generate node GW configuration: %v", err)) + errs = append(errs, fmt.Errorf("failed extracting node %q GW router join subnet IP for layer3 network %q: %w", + node.Name, oc.GetNetworkName(), err)) oc.gatewaysFailed.Store(node.Name, true) } else { - if err := gwManager.syncNodeGateway( - node, - gwConfig.config, - gwConfig.hostSubnets, - gwConfig.hostAddrs, - gwConfig.clusterSubnets, - gwConfig.gwLRPIPs, - oc.SCTPSupport, - oc.ovnClusterLRPToJoinIfAddrs, - gwConfig.externalIPs, - ); err != nil { - errs = append(errs, fmt.Errorf( - "failed to sync node GW for network %q: %v", - gwManager.netInfo.GetNetworkName(), - err, - )) + // parses node object for l3-gateway-config IP addresses + gwConfig, err := oc.nodeGatewayConfig(hostSubnets, gwLRPIPs, node) + if err != nil { + errs = append(errs, fmt.Errorf("failed to generate node GW configuration: %v", err)) oc.gatewaysFailed.Store(node.Name, true) } else { - oc.gatewaysFailed.Delete(node.Name) + // parses node object for node-primary-ifaddr and MTU support + if err := gwManager.syncNodeGateway( + node, + gwConfig.config, + gwConfig.hostSubnets, + gwConfig.hostAddrs, + gwConfig.clusterSubnets, + gwConfig.gwLRPIPs, + oc.SCTPSupport, + oc.ovnClusterLRPToJoinIfAddrs, + gwConfig.externalIPs, + ); err != nil { + errs = append(errs, fmt.Errorf( + "failed to sync node GW for network %q: %v", + gwManager.netInfo.GetNetworkName(), + err, + )) + oc.gatewaysFailed.Store(node.Name, true) + } else { + oc.gatewaysFailed.Delete(node.Name) + } } } } @@ -729,7 +829,7 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *kapi.N // if per pod SNAT is being used, then l3 gateway config is required to be able to add pods _, gwFailed := oc.gatewaysFailed.Load(node.Name) if !gwFailed || !config.Gateway.DisableSNATMultipleGWs { - if nSyncs.syncNode || nSyncs.syncGw { // do this only if it is a new node add or a gateway sync happened + if !syncSuccessful && (nSyncs.syncNode || nSyncs.syncGw) { // do this only if it is a new node add or a gateway sync happened errors := oc.addAllPodsOnNode(node.Name) errs = append(errs, errors...) } @@ -737,11 +837,23 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *kapi.N } if nSyncs.syncZoneIC && config.OVNKubernetesFeature.EnableInterconnect { - if err := oc.zoneICHandler.AddLocalZoneNode(node); err != nil { - errs = append(errs, err) - oc.syncZoneICFailed.Store(node.Name, true) - } else { - oc.syncZoneICFailed.Delete(node.Name) + if gwLRPIPs == nil { + gwLRPIPs, err = util.ParseNodeUDNGatewayRouterJoinAddrs(udnNode) + if err != nil { + errs = append(errs, fmt.Errorf("failed extracting node %q GW router join subnet IP for layer3 network %q: %w", + node.Name, oc.GetNetworkName(), err)) + oc.syncZoneICFailed.Store(node.Name, true) + } + if gwLRPIPs != nil { + // gets node ID from node annotation + // gets transit switch subnet + if err := oc.zoneICHandler.AddLocalZoneNode(hostSubnets, gwLRPIPs, node); err != nil { + errs = append(errs, err) + oc.syncZoneICFailed.Store(node.Name, true) + } else { + oc.syncZoneICFailed.Delete(node.Name) + } + } } } @@ -752,25 +864,37 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *kapi.N return err } -func (oc *SecondaryLayer3NetworkController) addUpdateRemoteNodeEvent(node *kapi.Node, syncZoneIc bool) error { +func (oc *SecondaryLayer3NetworkController) addUpdateRemoteNodeEvent(udnNode *userdefinednodeapi.UDNNode, node *kapi.Node, syncZoneIc bool) error { _, present := oc.localZoneNodes.Load(node.Name) if present { - if err := oc.deleteNodeEvent(node); err != nil { + if err := oc.deleteNodeEvent(node.Name); err != nil { return err } } - var err error if syncZoneIc && config.OVNKubernetesFeature.EnableInterconnect { - if err = oc.zoneICHandler.AddRemoteZoneNode(node); err != nil { - err = fmt.Errorf("failed to add the remote zone node [%s] to the zone interconnect handler, err : %v", node.Name, err) + hostSubnets, err := util.ParseNodeUDNHostSubnet(udnNode) + if err != nil || len(hostSubnets) < 1 { oc.syncZoneICFailed.Store(node.Name, true) + return fmt.Errorf("subnets in the node %q for the layer3 secondary network %s is missing : %w", node.Name, oc.GetNetworkName(), err) + } + + gwLRPIPs, err := util.ParseNodeUDNGatewayRouterJoinAddrs(udnNode) + if err != nil || len(gwLRPIPs) < 1 { + oc.syncZoneICFailed.Store(node.Name, true) + return fmt.Errorf("gateway LRP IPs in the node %q for the layer3 secondary network %s is missing : %w", node.Name, oc.GetNetworkName(), err) + } + + // needs node for transit switch subnet and node ID + if err = oc.zoneICHandler.AddRemoteZoneNode(hostSubnets, gwLRPIPs, node); err != nil { + oc.syncZoneICFailed.Store(node.Name, true) + return fmt.Errorf("failed to add the remote zone node [%s] to the zone interconnect handler, err : %v", node.Name, err) } else { oc.syncZoneICFailed.Delete(node.Name) } } - return err + return nil } // addNodeSubnetEgressSNAT adds the SNAT on each node's ovn-cluster-router in L3 networks @@ -783,12 +907,16 @@ func (oc *SecondaryLayer3NetworkController) addUpdateRemoteNodeEvent(node *kapi. // externalIP = "169.254.0.12"; which is the masqueradeIP for this L3 UDN // so all in all we want to condionally SNAT all packets that are coming from pods hosted on this node, // which are leaving via UDN's mpX interface to the UDN's masqueradeIP. -func (oc *SecondaryLayer3NetworkController) addUDNNodeSubnetEgressSNAT(localPodSubnets []*net.IPNet, node *kapi.Node) error { - outputPort := types.RouterToSwitchPrefix + oc.GetNetworkScopedName(node.Name) - nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort, node) +func (oc *SecondaryLayer3NetworkController) addUDNNodeSubnetEgressSNAT(localPodSubnets []*net.IPNet, nodeName string, udnNode *userdefinednodeapi.UDNNode) error { + outputPort := types.RouterToSwitchPrefix + oc.GetNetworkScopedName(nodeName) + mac, err := net.ParseMAC(udnNode.Spec.ManagementPortMACAddress) + if err != nil { + return fmt.Errorf("failed to parse MAC for network %q on node %q, mac string: %q", oc.GetNetworkName(), nodeName, udnNode.Spec.ManagementPortMACAddress) + } + nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort, nodeName, mac) if err != nil { return fmt.Errorf("failed to build UDN masquerade SNATs for network %q on node %q, err: %w", - oc.GetNetworkName(), node.Name, err) + oc.GetNetworkName(), nodeName, err) } if len(nats) == 0 { return nil // nothing to do @@ -803,50 +931,50 @@ func (oc *SecondaryLayer3NetworkController) addUDNNodeSubnetEgressSNAT(localPodS return nil } -func (oc *SecondaryLayer3NetworkController) addNode(node *kapi.Node) ([]*net.IPNet, error) { +func (oc *SecondaryLayer3NetworkController) addNode(udnNode *userdefinednodeapi.UDNNode, hostSubnets []*net.IPNet) error { // Node subnet for the secondary layer3 network is allocated by cluster manager. // Make sure that the node is allocated with the subnet before proceeding // to create OVN Northbound resources. - hostSubnets, err := util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) - if err != nil || len(hostSubnets) < 1 { - return nil, fmt.Errorf("subnet annotation in the node %q for the layer3 secondary network %s is missing : %w", node.Name, oc.GetNetworkName(), err) + nodeName := udnNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) } - err = oc.createNodeLogicalSwitch(node.Name, hostSubnets, oc.clusterLoadBalancerGroupUUID, oc.switchLoadBalancerGroupUUID) + err := oc.createNodeLogicalSwitch(nodeName, hostSubnets, oc.clusterLoadBalancerGroupUUID, oc.switchLoadBalancerGroupUUID) if err != nil { - return nil, err + return err } if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { - if err := oc.addUDNNodeSubnetEgressSNAT(hostSubnets, node); err != nil { - return nil, err + if err := oc.addUDNNodeSubnetEgressSNAT(hostSubnets, nodeName, udnNode); err != nil { + return err } } - return hostSubnets, nil + return nil } -func (oc *SecondaryLayer3NetworkController) deleteNodeEvent(node *kapi.Node) error { +func (oc *SecondaryLayer3NetworkController) deleteNodeEvent(nodeName string) error { klog.V(5).Infof("Deleting Node %q for network %s. Removing the node from "+ - "various caches", node.Name, oc.GetNetworkName()) + "various caches", nodeName, oc.GetNetworkName()) - if err := oc.deleteNode(node.Name); err != nil { + if err := oc.deleteNode(nodeName); err != nil { return err } - if err := oc.gatewayManagerForNode(node.Name).Cleanup(); err != nil { - return fmt.Errorf("failed to cleanup gateway on node %q: %w", node.Name, err) + if err := oc.gatewayManagerForNode(nodeName).Cleanup(); err != nil { + return fmt.Errorf("failed to cleanup gateway on node %q: %w", nodeName, err) } - oc.gatewayManagers.Delete(node.Name) - oc.localZoneNodes.Delete(node.Name) + oc.gatewayManagers.Delete(nodeName) + oc.localZoneNodes.Delete(nodeName) - oc.lsManager.DeleteSwitch(oc.GetNetworkScopedName(node.Name)) - oc.addNodeFailed.Delete(node.Name) - oc.mgmtPortFailed.Delete(node.Name) - oc.nodeClusterRouterPortFailed.Delete(node.Name) + oc.lsManager.DeleteSwitch(oc.GetNetworkScopedName(nodeName)) + oc.addNodeFailed.Delete(nodeName) + oc.mgmtPortFailed.Delete(nodeName) + oc.nodeClusterRouterPortFailed.Delete(nodeName) if config.OVNKubernetesFeature.EnableInterconnect { - if err := oc.zoneICHandler.DeleteNode(node); err != nil { + if err := oc.zoneICHandler.DeleteNode(nodeName); err != nil { return err } - oc.syncZoneICFailed.Delete(node.Name) + oc.syncZoneICFailed.Delete(nodeName) } return nil } @@ -927,7 +1055,7 @@ type SecondaryL3GatewayConfig struct { externalIPs []net.IP } -func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *kapi.Node) (*SecondaryL3GatewayConfig, error) { +func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(hostSubnets, gwLRPIPs []*net.IPNet, node *kapi.Node) (*SecondaryL3GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return nil, fmt.Errorf("failed to get node %s network %s L3 gateway config: %v", node.Name, oc.GetNetworkName(), err) @@ -966,17 +1094,6 @@ func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *kapi.Node) ( clusterSubnets = append(clusterSubnets, subnet.CIDR) } - // Fetch the host subnets present in the node annotation for this network - hostSubnets, err := util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) - if err != nil { - return nil, fmt.Errorf("failed to get node %q subnet annotation for network %q: %v", node.Name, oc.GetNetworkName(), err) - } - - gwLRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, oc.GetNetworkName()) - if err != nil { - return nil, fmt.Errorf("failed extracting node %q GW router join subnet IP for layer3 network %q: %w", node.Name, networkName, err) - } - // Overwrite the primary interface ID with the correct, per-network one. l3GatewayConfig.InterfaceID = oc.GetNetworkScopedExtPortName(l3GatewayConfig.BridgeID, node.Name) diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index 5136378814..0f5071cc42 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -215,7 +215,7 @@ func (zic *ZoneInterconnectHandler) ensureTransitSwitch(nodes []*corev1.Node) er // AddLocalZoneNode creates the interconnect resources in OVN NB DB for the local zone node. // See createLocalZoneNodeResources() below for more details. -func (zic *ZoneInterconnectHandler) AddLocalZoneNode(node *corev1.Node) error { +func (zic *ZoneInterconnectHandler) AddLocalZoneNode(hostSubnets, joinSubnets []*net.IPNet, node *corev1.Node) error { klog.Infof("Creating interconnect resources for local zone node %s for the network %s", node.Name, zic.GetNetworkName()) nodeID := util.GetNodeID(node) if nodeID == -1 { @@ -223,7 +223,7 @@ func (zic *ZoneInterconnectHandler) AddLocalZoneNode(node *corev1.Node) error { return fmt.Errorf("failed to get node id for node - %s", node.Name) } - if err := zic.createLocalZoneNodeResources(node, nodeID); err != nil { + if err := zic.createLocalZoneNodeResources(hostSubnets, joinSubnets, node, nodeID); err != nil { return fmt.Errorf("creating interconnect resources for local zone node %s for the network %s failed : err - %w", node.Name, zic.GetNetworkName(), err) } @@ -232,7 +232,7 @@ func (zic *ZoneInterconnectHandler) AddLocalZoneNode(node *corev1.Node) error { // AddRemoteZoneNode creates the interconnect resources in OVN NBDB and SBDB for the remote zone node. // // See createRemoteZoneNodeResources() below for more details. -func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(node *corev1.Node) error { +func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(nodeSubnets, gwLRPIPs []*net.IPNet, node *corev1.Node) error { start := time.Now() klog.Infof("Creating interconnect resources for remote zone node %s for the network %s", node.Name, zic.GetNetworkName()) @@ -242,13 +242,16 @@ func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(node *corev1.Node) error { return fmt.Errorf("failed to get node id for node - %s", node.Name) } - // Get the chassis id. - chassisId, err := util.ParseNodeChassisIDAnnotation(node) - if err != nil { - return fmt.Errorf("failed to parse node chassis-id for node - %s, error: %w", node.Name, types.NewSuppressedError(err)) + nodeTransitSwitchPortIPs, err := util.ParseNodeTransitSwitchPortAddrs(node) + if err != nil || len(nodeTransitSwitchPortIPs) == 0 { + err = fmt.Errorf("failed to get the node transit switch port IP addresses : %w", err) + if util.IsAnnotationNotSetError(err) { + return types.NewSuppressedError(err) + } + return err } - if err := zic.createRemoteZoneNodeResources(node, nodeID, chassisId); err != nil { + if err := zic.createRemoteZoneNodeResources(nodeSubnets, gwLRPIPs, nodeTransitSwitchPortIPs, node.Name, nodeID); err != nil { return fmt.Errorf("creating interconnect resources for remote zone node %s for the network %s failed : err - %w", node.Name, zic.GetNetworkName(), err) } klog.Infof("Creating Interconnect resources for node %v took: %s", node.Name, time.Since(start)) @@ -256,10 +259,10 @@ func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(node *corev1.Node) error { } // DeleteNode deletes the local zone node or remote zone node resources -func (zic *ZoneInterconnectHandler) DeleteNode(node *corev1.Node) error { - klog.Infof("Deleting interconnect resources for the node %s for the network %s", node.Name, zic.GetNetworkName()) +func (zic *ZoneInterconnectHandler) DeleteNode(nodeName string) error { + klog.Infof("Deleting interconnect resources for the node %s for the network %s", nodeName, zic.GetNetworkName()) - return zic.cleanupNode(node.Name) + return zic.cleanupNode(nodeName) } // SyncNodes ensures a transit switch exists and cleans up the interconnect @@ -384,7 +387,7 @@ func (zic *ZoneInterconnectHandler) addTransitSwitchConfig(sw *nbdb.LogicalSwitc // - creates a logical router port in the ovn_cluster_router with the name - .rtots- and connects // to the node logical switch port in the transit switch // - remove any stale static routes in the ovn_cluster_router for the node -func (zic *ZoneInterconnectHandler) createLocalZoneNodeResources(node *corev1.Node, nodeID int) error { +func (zic *ZoneInterconnectHandler) createLocalZoneNodeResources(hostSubnets, joinSubnets []*net.IPNet, node *corev1.Node, nodeID int) error { nodeTransitSwitchPortIPs, err := util.ParseNodeTransitSwitchPortAddrs(node) if err != nil || len(nodeTransitSwitchPortIPs) == 0 { return fmt.Errorf("failed to get the node transit switch port ips for node %s: %w", node.Name, err) @@ -431,7 +434,7 @@ func (zic *ZoneInterconnectHandler) createLocalZoneNodeResources(node *corev1.No // Its possible that node is moved from a remote zone to the local zone. Check and delete the remote zone routes // for this node as it's no longer needed. - return zic.deleteLocalNodeStaticRoutes(node, nodeTransitSwitchPortIPs) + return zic.deleteLocalNodeStaticRoutes(hostSubnets, joinSubnets, node, nodeTransitSwitchPortIPs) } // createRemoteZoneNodeResources creates the remote zone node resources @@ -441,16 +444,7 @@ func (zic *ZoneInterconnectHandler) createLocalZoneNodeResources(node *corev1.No // if the node name is ovn-worker and the network name is blue, the logical port name would be - blue.tstor.ovn-worker // - binds the remote port to the node remote chassis in SBDB // - adds static routes for the remote node via the remote port ip in the ovn_cluster_router -func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(node *corev1.Node, nodeID int, chassisId string) error { - nodeTransitSwitchPortIPs, err := util.ParseNodeTransitSwitchPortAddrs(node) - if err != nil || len(nodeTransitSwitchPortIPs) == 0 { - err = fmt.Errorf("failed to get the node transit switch port IP addresses : %w", err) - if util.IsAnnotationNotSetError(err) { - return types.NewSuppressedError(err) - } - return err - } - +func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(nodeSubnets, nodeGRPIPs, nodeTransitSwitchPortIPs []*net.IPNet, nodeName string, nodeID int) error { transitRouterPortMac := util.IPAddrToHWAddr(nodeTransitSwitchPortIPs[0].IP) var transitRouterPortNetworks []string for _, ip := range nodeTransitSwitchPortIPs { @@ -464,25 +458,25 @@ func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(node *corev1.N lspOptions := map[string]string{ "requested-tnl-key": strconv.Itoa(nodeID), - "requested-chassis": node.Name, + "requested-chassis": nodeName, } // Store the node name in the external_ids column for book keeping externalIDs := map[string]string{ - "node": node.Name, + "node": nodeName, } - remotePortName := zic.GetNetworkScopedName(types.TransitSwitchToRouterPrefix + node.Name) + remotePortName := zic.GetNetworkScopedName(types.TransitSwitchToRouterPrefix + nodeName) if err := zic.addNodeLogicalSwitchPort(zic.networkTransitSwitchName, remotePortName, lportTypeRemote, []string{remotePortAddr}, lspOptions, externalIDs); err != nil { return err } - if err := zic.addRemoteNodeStaticRoutes(node, nodeTransitSwitchPortIPs); err != nil { + if err := zic.addRemoteNodeStaticRoutes(nodeSubnets, nodeGRPIPs, nodeName, nodeTransitSwitchPortIPs); err != nil { return err } // Cleanup the logical router port connecting to the transit switch for the remote node (if present) // Cleanup would be required when a local zone node moves to a remote zone. - return zic.cleanupNodeClusterRouterPort(node.Name) + return zic.cleanupNodeClusterRouterPort(nodeName) } func (zic *ZoneInterconnectHandler) addNodeLogicalSwitchPort(logicalSwitchName, portName, portType string, addresses []string, options, externalIDs map[string]string) error { @@ -572,11 +566,11 @@ func (zic *ZoneInterconnectHandler) cleanupNodeTransitSwitchPort(nodeName string // Then the below static routes are added // ip4.dst == 10.244.0.0/24 , nexthop = 100.88.0.2 // ip4.dst == 100.64.0.2/16 , nexthop = 100.88.0.2 (only for default primary network) -func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs []*net.IPNet) error { +func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(nodeSubnets, nodeGRPIPs []*net.IPNet, nodeName string, nodeTransitSwitchPortIPs []*net.IPNet) error { addRoute := func(prefix, nexthop string) error { logicalRouterStaticRoute := nbdb.LogicalRouterStaticRoute{ ExternalIDs: map[string]string{ - "ic-node": node.Name, + "ic-node": nodeName, }, Nexthop: nexthop, IPPrefix: prefix, @@ -584,7 +578,7 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool { return lrsr.IPPrefix == prefix && lrsr.Nexthop == nexthop && - lrsr.ExternalIDs["ic-node"] == node.Name + lrsr.ExternalIDs["ic-node"] == nodeName } if err := libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(zic.nbClient, zic.networkClusterRouterName, &logicalRouterStaticRoute, p); err != nil { return fmt.Errorf("failed to create static route: %w", err) @@ -592,16 +586,6 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, return nil } - nodeSubnets, err := util.ParseNodeHostSubnetAnnotation(node, zic.GetNetworkName()) - if err != nil { - err = fmt.Errorf("failed to parse node %s subnets annotation %w", node.Name, err) - if util.IsAnnotationNotSetError(err) { - // remote node may not have the annotation yet, suppress it - return types.NewSuppressedError(err) - } - return err - } - nodeSubnetStaticRoutes := zic.getStaticRoutes(nodeSubnets, nodeTransitSwitchPortIPs, false) for _, staticRoute := range nodeSubnetStaticRoutes { // Possible optimization: Add all the routes in one transaction @@ -618,23 +602,6 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, return nil } - nodeGRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, zic.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // FIXME(tssurya): This is present for backwards compatibility - // Remove me a few months from now - var err1 error - nodeGRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) - if err1 != nil { - err1 = fmt.Errorf("failed to parse node %s Gateway router LRP Addrs annotation %w", node.Name, err1) - if util.IsAnnotationNotSetError(err1) { - return types.NewSuppressedError(err1) - } - return err1 - } - } - } - nodeGRPIPStaticRoutes := zic.getStaticRoutes(nodeGRPIPs, nodeTransitSwitchPortIPs, true) for _, staticRoute := range nodeGRPIPStaticRoutes { // Possible optimization: Add all the routes in one transaction @@ -647,7 +614,7 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, } // deleteLocalNodeStaticRoutes deletes the static routes added by the function addRemoteNodeStaticRoutes -func (zic *ZoneInterconnectHandler) deleteLocalNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs []*net.IPNet) error { +func (zic *ZoneInterconnectHandler) deleteLocalNodeStaticRoutes(nodeSubnets, nodeGRPIPs []*net.IPNet, node *corev1.Node, nodeTransitSwitchPortIPs []*net.IPNet) error { deleteRoute := func(prefix, nexthop string) error { p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool { return lrsr.IPPrefix == prefix && @@ -660,11 +627,6 @@ func (zic *ZoneInterconnectHandler) deleteLocalNodeStaticRoutes(node *corev1.Nod return nil } - nodeSubnets, err := util.ParseNodeHostSubnetAnnotation(node, zic.GetNetworkName()) - if err != nil { - return fmt.Errorf("failed to parse node %s subnets annotation %w", node.Name, err) - } - nodeSubnetStaticRoutes := zic.getStaticRoutes(nodeSubnets, nodeTransitSwitchPortIPs, false) for _, staticRoute := range nodeSubnetStaticRoutes { // Possible optimization: Add all the routes in one transaction @@ -679,20 +641,6 @@ func (zic *ZoneInterconnectHandler) deleteLocalNodeStaticRoutes(node *corev1.Nod return nil } - // Clear the routes connecting to the GW Router for the default network - nodeGRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, zic.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // FIXME(tssurya): This is present for backwards compatibility - // Remove me a few months from now - var err1 error - nodeGRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) - if err1 != nil { - return fmt.Errorf("failed to parse node %s Gateway router LRP Addrs annotation %w", node.Name, err1) - } - } - } - nodenodeGRPIPStaticRoutes := zic.getStaticRoutes(nodeGRPIPs, nodeTransitSwitchPortIPs, true) for _, staticRoute := range nodenodeGRPIPStaticRoutes { // Possible optimization: Add all the routes in one transaction diff --git a/go-controller/pkg/util/kube.go b/go-controller/pkg/util/kube.go index ae1e75fd3a..9cf74c6e73 100644 --- a/go-controller/pkg/util/kube.go +++ b/go-controller/pkg/util/kube.go @@ -7,6 +7,7 @@ import ( "crypto/x509/pkix" "errors" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" "os" "path/filepath" @@ -719,6 +720,14 @@ func NoHostSubnet(node *kapi.Node) bool { return config.Kubernetes.NoHostSubnetNodes.Matches(labels.Set(node.Labels)) } +func NoHostSubnetUDNNode(udnNode *userdefinednodeapi.UDNNode) bool { + if config.Kubernetes.NoHostSubnetNodes == nil { + return false + } + + return config.Kubernetes.NoHostSubnetNodes.Matches(labels.Set(udnNode.Labels)) +} + // getSelectedEligibleEndpoints does the following: // (1) filters the given endpoints with the provided condition function condFn; // (2) further selects eligible endpoints based on readiness. diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index 34a6fa9d6f..ae450e4b09 100644 --- a/go-controller/pkg/util/node_annotations.go +++ b/go-controller/pkg/util/node_annotations.go @@ -3,6 +3,7 @@ package util import ( "encoding/json" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "math" "net" "net/netip" @@ -904,6 +905,18 @@ func ParseNodeGatewayRouterJoinIPv4(node *kapi.Node, netName string) (net.IP, er return ip, nil } +func ParseNodeUDNGatewayRouterJoinAddrs(udnNode *userdefinednodeapi.UDNNode) ([]*net.IPNet, error) { + var nets []*net.IPNet + for _, subnet := range udnNode.Spec.JoinSubnets { + _, ipnet, err := net.ParseCIDR(string(subnet)) + if err != nil { + return nil, err + } + nets = append(nets, ipnet) + } + return nets, nil +} + // ParseNodeGatewayRouterJoinAddrs returns the IPv4 and/or IPv6 addresses for the node's gateway router port // stored in the 'OVNNodeGRLRPAddrs' annotation func ParseNodeGatewayRouterJoinAddrs(node *kapi.Node, netName string) ([]*net.IPNet, error) { @@ -920,6 +933,10 @@ func ParseNodeTransitSwitchPortAddrs(node *kapi.Node) ([]*net.IPNet, error) { return parsePrimaryIfAddrAnnotation(node, ovnTransitSwitchPortAddr) } +func TransitSwitchAddrsEqual(old, new *kapi.Node) bool { + return old.Annotations[ovnTransitSwitchPortAddr] == new.Annotations[ovnTransitSwitchPortAddr] +} + // ParseNodeMasqueradeSubnet returns the IPv4 and/or IPv6 networks for the node's gateway router port // stored in the 'OvnNodeMasqCIDR' annotation func ParseNodeMasqueradeSubnet(node *kapi.Node) ([]*net.IPNet, error) { diff --git a/go-controller/pkg/util/subnet_annotations.go b/go-controller/pkg/util/subnet_annotations.go index 11964dfbb8..0740b1dea7 100644 --- a/go-controller/pkg/util/subnet_annotations.go +++ b/go-controller/pkg/util/subnet_annotations.go @@ -3,6 +3,7 @@ package util import ( "encoding/json" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" kapi "k8s.io/api/core/v1" @@ -165,6 +166,18 @@ func DeleteNodeHostSubnetAnnotation(nodeAnnotator kube.Annotator) { nodeAnnotator.Delete(ovnNodeSubnets) } +func ParseNodeUDNHostSubnet(udnNode *userdefinednodeapi.UDNNode) ([]*net.IPNet, error) { + nets := []*net.IPNet{} + for _, subnet := range udnNode.Spec.NodeSubnets { + _, ipnet, err := net.ParseCIDR(string(subnet)) + if err != nil { + return nil, err + } + nets = append(nets, ipnet) + } + return nets, nil +} + // ParseNodeHostSubnetAnnotation parses the "k8s.ovn.org/node-subnets" annotation // on a node and returns the host subnet for the given network. func ParseNodeHostSubnetAnnotation(node *kapi.Node, netName string) ([]*net.IPNet, error) { From fa93ea00c97240550faa6fb7107de5dfc373601f Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 19 Nov 2024 15:21:42 -0500 Subject: [PATCH 04/17] Adds support for layer 2 Signed-off-by: Tim Rozet --- go-controller/pkg/crd/udnnode/v1/types.go | 2 + go-controller/pkg/factory/factory.go | 3 + .../pkg/ovn/base_network_controller.go | 4 +- .../ovn/base_network_controller_secondary.go | 7 +- .../secondary_layer2_network_controller.go | 252 ++++++++++++------ .../secondary_layer3_network_controller.go | 25 +- go-controller/pkg/util/node_annotations.go | 3 + 7 files changed, 199 insertions(+), 97 deletions(-) diff --git a/go-controller/pkg/crd/udnnode/v1/types.go b/go-controller/pkg/crd/udnnode/v1/types.go index bfbfa0a8d6..86f45348ab 100644 --- a/go-controller/pkg/crd/udnnode/v1/types.go +++ b/go-controller/pkg/crd/udnnode/v1/types.go @@ -61,6 +61,8 @@ type UDNNodeSpec struct { JoinSubnets DualStackCIDRs `json:"joinSubnets,omitempty"` ManagementPortMACAddress string `json:"managementPortMACAddress,omitempty"` + + Layer2TunnelID int `json:"layer2TunnelID,omitempty"` } // UDNNodeStatus defines the observed state of UDNNode diff --git a/go-controller/pkg/factory/factory.go b/go-controller/pkg/factory/factory.go index fffab22b77..ed2387beb5 100644 --- a/go-controller/pkg/factory/factory.go +++ b/go-controller/pkg/factory/factory.go @@ -900,6 +900,9 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset return nil, err } wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, wf.udnNodeFactory.K8s().V1().UDNNodes().Informer()) + if err != nil { + return nil, err + } // make sure namespace informer cache is initialized and synced on Start(). wf.iFactory.Core().V1().Namespaces().Informer() diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index ed49e91bae..262f9c1a8c 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -673,7 +673,7 @@ func (bnc *BaseNetworkController) syncNodeManagementPort(macAddress net.Hardware Addresses: []string{addresses}, } sw := nbdb.LogicalSwitch{Name: switchName} - err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(bnc.nbClient, &sw, &logicalSwitchPort) + err := libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(bnc.nbClient, &sw, &logicalSwitchPort) if err != nil { return nil, err } @@ -691,7 +691,7 @@ func (bnc *BaseNetworkController) syncNodeManagementPort(macAddress net.Hardware } if v4Subnet != nil { - if err := libovsdbutil.UpdateNodeSwitchExcludeIPs(bnc.nbClient, bnc.GetNetworkScopedK8sMgmtIntfName(node.Name), + if err := libovsdbutil.UpdateNodeSwitchExcludeIPs(bnc.nbClient, bnc.GetNetworkScopedK8sMgmtIntfName(nodeName), bnc.GetNetworkScopedSwitchName(nodeName), nodeName, v4Subnet); err != nil { return nil, err } diff --git a/go-controller/pkg/ovn/base_network_controller_secondary.go b/go-controller/pkg/ovn/base_network_controller_secondary.go index 10942ec642..5da4002879 100644 --- a/go-controller/pkg/ovn/base_network_controller_secondary.go +++ b/go-controller/pkg/ovn/base_network_controller_secondary.go @@ -817,11 +817,6 @@ func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets [ if err != nil { return nil, fmt.Errorf("failed to get networkID for network %q: %v", bsnc.GetNetworkName(), err) } - dstMac, err := util.ParseNodeManagementPortMACAddresses(macAddr, bsnc.GetNetworkName()) - if err != nil { - return nil, fmt.Errorf("failed to parse mac address annotation for network %q on node %q, err: %w", - bsnc.GetNetworkName(), nodeName, err) - } extIDs := map[string]string{ types.NetworkExternalID: bsnc.GetNetworkName(), types.TopologyExternalID: bsnc.TopologyType(), @@ -839,7 +834,7 @@ func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets [ return nil, fmt.Errorf("masquerade IP cannot be empty network %s (%d): %v", bsnc.GetNetworkName(), networkID, err) } snats = append(snats, libovsdbops.BuildSNATWithMatch(&masqIP.ManagementPort.IP, localPodSubnet, outputPort, - extIDs, getMasqueradeManagementIPSNATMatch(dstMac.String()))) + extIDs, getMasqueradeManagementIPSNATMatch(macAddr.String()))) } return snats, nil } diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 21fb77b84f..6d42470fb8 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -12,6 +12,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/pod" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" @@ -99,10 +100,23 @@ func (h *secondaryLayer2NetworkControllerEventHandler) IsResourceScheduled(obj i // Given an object to add and a boolean specifying if the function was executed from iterateRetryResources func (h *secondaryLayer2NetworkControllerEventHandler) AddResource(obj interface{}, fromRetryLoop bool) error { switch h.objType { - case factory.NodeType: - node, ok := obj.(*corev1.Node) + case factory.UserDefinedNodeType: + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { - return fmt.Errorf("could not cast %T object to Node", obj) + return fmt.Errorf("could not cast %T object to *userdefinednodeapi.UDNNode", obj) + } + + if h.oc.GetNetworkName() != udnNode.GetLabels()["networkName"] { + return nil + } + nodeName := udnNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) + } + + node, err := h.watchFactory.GetNode(nodeName) + if err != nil { + return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, udnNode.Name) } if h.oc.isLocalZoneNode(node) { var nodeParams *nodeSyncs @@ -113,9 +127,9 @@ func (h *secondaryLayer2NetworkControllerEventHandler) AddResource(obj interface } else { nodeParams = &nodeSyncs{syncMgmtPort: true, syncGw: true} } - return h.oc.addUpdateLocalNodeEvent(node, nodeParams) + return h.oc.addUpdateLocalNodeEvent(udnNode, node, nodeParams) } - return h.oc.addUpdateRemoteNodeEvent(node, config.OVNKubernetesFeature.EnableInterconnect) + return h.oc.addUpdateRemoteNodeEvent(udnNode, node, config.OVNKubernetesFeature.EnableInterconnect) default: return h.oc.AddSecondaryNetworkResourceCommon(h.objType, obj) } @@ -126,12 +140,21 @@ func (h *secondaryLayer2NetworkControllerEventHandler) AddResource(obj interface // used for now for pods and network policies. func (h *secondaryLayer2NetworkControllerEventHandler) DeleteResource(obj, cachedObj interface{}) error { switch h.objType { - case factory.NodeType: - node, ok := obj.(*corev1.Node) + case factory.UserDefinedNodeType: + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { - return fmt.Errorf("could not cast %T object to Node", obj) + return fmt.Errorf("could not cast obj of type %T to *knet.Node", obj) + } + if h.oc.GetNetworkName() != udnNode.GetLabels()["networkName"] { + return nil } - return h.oc.deleteNodeEvent(node) + nodeName := udnNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) + } + return h.oc.deleteNodeEvent(nodeName) + case factory.NodeType: + return nil default: return h.oc.DeleteSecondaryNetworkResourceCommon(h.objType, obj, cachedObj) } @@ -153,34 +176,85 @@ func (h *secondaryLayer2NetworkControllerEventHandler) UpdateResource(oldObj, ne return fmt.Errorf("could not cast oldObj of type %T to *kapi.Node", oldObj) } newNodeIsLocalZoneNode := h.oc.isLocalZoneNode(newNode) - nodeSubnetChanged := nodeSubnetChanged(oldNode, newNode, h.oc.NetInfo.GetNetworkName()) + needsResync := false if newNodeIsLocalZoneNode { - var nodeSyncsParam *nodeSyncs if h.oc.isLocalZoneNode(oldNode) { // determine what actually changed in this update and combine that with what failed previously - _, mgmtUpdateFailed := h.oc.mgmtPortFailed.Load(newNode.Name) - shouldSyncMgmtPort := mgmtUpdateFailed || - macAddressChanged(oldNode, newNode, h.oc.NetInfo.GetNetworkName()) || - nodeSubnetChanged - _, gwUpdateFailed := h.oc.gatewaysFailed.Load(newNode.Name) - shouldSyncGW := gwUpdateFailed || - gatewayChanged(oldNode, newNode) || - hostCIDRsChanged(oldNode, newNode) || - nodeGatewayMTUSupportChanged(oldNode, newNode) - - nodeSyncsParam = &nodeSyncs{syncMgmtPort: shouldSyncMgmtPort, syncGw: shouldSyncGW} + // TODO(trozet): why do we care if host cidrs changed? + if gatewayChanged(oldNode, newNode) || hostCIDRsChanged(oldNode, newNode) || nodeGatewayMTUSupportChanged(oldNode, newNode) { + h.oc.mgmtPortFailed.Store(newNode.Name, true) + h.oc.gatewaysFailed.Store(newNode.Name, true) + needsResync = true + } } else { + // node moved from remote -> local klog.Infof("Node %s moved from the remote zone %s to local zone %s.", newNode.Name, util.GetNodeZone(oldNode), util.GetNodeZone(newNode)) - // The node is now a local zone node. Trigger a full node sync. - nodeSyncsParam = &nodeSyncs{syncMgmtPort: true, syncGw: true} + h.oc.mgmtPortFailed.Store(newNode.Name, true) + h.oc.gatewaysFailed.Store(newNode.Name, true) + // TODO(trozet): should we also be setting sync IC here? was not in old code + needsResync = true } + } else { + // TROZET: looks like there was a bug here already where we are not forcing resync if local -> remote + if h.oc.isLocalZoneNode(oldNode) { + klog.Infof("Node %s moved from the local zone %s to remote zone %s.", + newNode.Name, util.GetNodeZone(oldNode), util.GetNodeZone(newNode)) + // node is moving from local to remote + h.oc.syncZoneICFailed.Store(newNode.Name, true) + needsResync = true + } + // no other node changes for remote really matter for setting up layer 2 networks + } + + if needsResync { + networkID, err := h.oc.getNetworkID() + if err != nil { + return err + } + // reset + if err := h.oc.retryUDNNodes.AddRetryObjWithAddNoBackoff(fmt.Sprintf("%d-%s", networkID, newNode.Name)); err != nil { + return err + } + } + return nil + case factory.UserDefinedNodeType: + newUDNNode, ok := newObj.(*userdefinednodeapi.UDNNode) + if !ok { + return fmt.Errorf("could not cast newObj of type %T to *userdefinednodeapi.UDNNode", newObj) + } + oldUDNNode, ok := oldObj.(*userdefinednodeapi.UDNNode) + if !ok { + return fmt.Errorf("could not cast oldObj of type %T to *userdefinednodeapi.UDNNode", oldObj) + } + if h.oc.GetNetworkName() != newUDNNode.GetLabels()["networkName"] { + return nil + } + nodeName := newUDNNode.GetLabels()["nodeName"] + if nodeName == "" { + return fmt.Errorf("unable to find nodeName label for udn Node: %s", newUDNNode.Name) + } + + node, err := h.watchFactory.GetNode(nodeName) + if err != nil { + return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, newUDNNode.Name) + } - return h.oc.addUpdateLocalNodeEvent(newNode, nodeSyncsParam) + if h.oc.isLocalZoneNode(node) { + var nodeSyncsParam *nodeSyncs + // determine what actually changed in this update and combine that with what failed previously + _, mgmtUpdateFailed := h.oc.mgmtPortFailed.Load(node.Name) + shouldSyncMgmtPort := mgmtUpdateFailed || udnNodeMACAddressChanged(oldUDNNode, newUDNNode) || udnNodeSubnetChanged(oldUDNNode, newUDNNode) + + _, gwUpdateFailed := h.oc.gatewaysFailed.Load(node.Name) + shouldSyncGW := gwUpdateFailed + nodeSyncsParam = &nodeSyncs{syncMgmtPort: shouldSyncMgmtPort, syncGw: shouldSyncGW} + return h.oc.addUpdateLocalNodeEvent(newUDNNode, node, nodeSyncsParam) } else { - _, syncZoneIC := h.oc.syncZoneICFailed.Load(newNode.Name) - return h.oc.addUpdateRemoteNodeEvent(newNode, syncZoneIC) + _, syncZoneIC := h.oc.syncZoneICFailed.Load(node.Name) + return h.oc.addUpdateRemoteNodeEvent(newUDNNode, node, syncZoneIC) } + default: return h.oc.UpdateSecondaryNetworkResourceCommon(h.objType, oldObj, newObj, inRetryCache) } @@ -492,19 +566,31 @@ func (oc *SecondaryLayer2NetworkController) newRetryFramework( ) } -func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1.Node, nSyncs *nodeSyncs) error { +func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(udnNode *userdefinednodeapi.UDNNode, node *corev1.Node, nSyncs *nodeSyncs) error { var errs []error - + var macAddr net.HardwareAddr if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { if nSyncs.syncGw { + gwManager := oc.gatewayManagerForNode(node.Name) oc.gatewayManagers.Store(node.Name, gwManager) - gwConfig, err := oc.nodeGatewayConfig(node) + gwLRPIPs, err := util.ParseNodeUDNGatewayRouterJoinAddrs(udnNode) + if err != nil { + oc.gatewaysFailed.Store(node.Name, true) + + errs = append(errs, fmt.Errorf("failed extracting node %q GW router join subnet IP for layer3 network %q: %w", + node.Name, oc.GetNetworkName(), err)) + } + + // gets l3 gw config from node + gwConfig, err := oc.nodeGatewayConfig(gwLRPIPs, node) if err != nil { errs = append(errs, err) oc.gatewaysFailed.Store(node.Name, true) - } else { + } + + if err != nil && len(gwLRPIPs) > 0 { if err := gwManager.syncNodeGateway( node, gwConfig.config, @@ -520,11 +606,17 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 oc.gatewaysFailed.Store(node.Name, true) } else { if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { - if err := oc.addUDNClusterSubnetEgressSNAT(gwConfig.hostSubnets, gwManager.gwRouterName, node); err != nil { - errs = append(errs, err) + macAddr, err = net.ParseMAC(udnNode.Spec.ManagementPortMACAddress) + if err != nil { + errs = append(errs, fmt.Errorf("failed to parse MAC for network %q on node %q, mac string: %q", oc.GetNetworkName(), node.Name, udnNode.Spec.ManagementPortMACAddress)) oc.gatewaysFailed.Store(node.Name, true) } else { - oc.gatewaysFailed.Delete(node.Name) + if err := oc.addUDNClusterSubnetEgressSNAT(macAddr, gwConfig.hostSubnets, gwManager.gwRouterName, node.Name); err != nil { + errs = append(errs, err) + oc.gatewaysFailed.Store(node.Name, true) + } else { + oc.gatewaysFailed.Delete(node.Name) + } } } } @@ -539,15 +631,26 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 for _, subnet := range oc.Subnets() { hostSubnets = append(hostSubnets, subnet.CIDR) } - if _, err := oc.syncNodeManagementPort(node, oc.GetNetworkScopedSwitchName(types.OVNLayer2Switch), oc.GetNetworkScopedGWRouterName(node.Name), hostSubnets); err != nil { - errs = append(errs, err) - oc.mgmtPortFailed.Store(node.Name, true) - } else { - oc.mgmtPortFailed.Delete(node.Name) + var err error + if macAddr == nil { + macAddr, err = net.ParseMAC(udnNode.Spec.ManagementPortMACAddress) + if err != nil { + errs = append(errs, fmt.Errorf("failed to parse MAC for network %q on node %q, mac string: %q", oc.GetNetworkName(), node.Name, udnNode.Spec.ManagementPortMACAddress)) + oc.mgmtPortFailed.Store(node.Name, true) + } + } + if macAddr != nil { + if _, err := oc.syncNodeManagementPort(macAddr, node.Name, oc.GetNetworkScopedSwitchName(types.OVNLayer2Switch), oc.GetNetworkScopedGWRouterName(node.Name), hostSubnets); err != nil { + errs = append(errs, err) + oc.mgmtPortFailed.Store(node.Name, true) + } else { + oc.mgmtPortFailed.Delete(node.Name) + } } } } + // only uses node name for this path, but localnet relies on full node errs = append(errs, oc.BaseSecondaryLayer2NetworkController.addUpdateLocalNodeEvent(node)) err := utilerrors.Join(errs...) @@ -557,12 +660,19 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 return err } -func (oc *SecondaryLayer2NetworkController) addUpdateRemoteNodeEvent(node *corev1.Node, syncZoneIC bool) error { +func (oc *SecondaryLayer2NetworkController) addUpdateRemoteNodeEvent(udnNode *userdefinednodeapi.UDNNode, node *corev1.Node, syncZoneIC bool) error { var errs []error if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { if syncZoneIC && config.OVNKubernetesFeature.EnableInterconnect { - if err := oc.addPortForRemoteNodeGR(node); err != nil { + gwLRPIPs, err := util.ParseNodeUDNGatewayRouterJoinAddrs(udnNode) + if err != nil { + oc.syncZoneICFailed.Store(node.Name, true) + errs = append(errs, fmt.Errorf("failed extracting node %q GW router join subnet IP for layer3 network %q: %w", + node.Name, oc.GetNetworkName(), err)) + } + + if err := oc.addPortForRemoteNodeGR(gwLRPIPs, udnNode, node.Name); err != nil { err = fmt.Errorf("failed to add the remote zone node %s's remote LRP, %w", node.Name, err) errs = append(errs, err) oc.syncZoneICFailed.Store(node.Name, true) @@ -572,6 +682,7 @@ func (oc *SecondaryLayer2NetworkController) addUpdateRemoteNodeEvent(node *corev } } + // just uses node name errs = append(errs, oc.BaseSecondaryLayer2NetworkController.addUpdateRemoteNodeEvent(node)) err := utilerrors.Join(errs...) @@ -581,17 +692,9 @@ func (oc *SecondaryLayer2NetworkController) addUpdateRemoteNodeEvent(node *corev return err } -func (oc *SecondaryLayer2NetworkController) addPortForRemoteNodeGR(node *corev1.Node) error { - nodeJoinSubnetIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, oc.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // remote node may not have the annotation yet, suppress it - return types.NewSuppressedError(err) - } - return fmt.Errorf("failed to get the node %s join subnet IPs: %w", node.Name, err) - } +func (oc *SecondaryLayer2NetworkController) addPortForRemoteNodeGR(nodeJoinSubnetIPs []*net.IPNet, udnNode *userdefinednodeapi.UDNNode, nodeName string) error { if len(nodeJoinSubnetIPs) == 0 { - return fmt.Errorf("annotation on the node %s had empty join subnet IPs", node.Name) + return fmt.Errorf("annotation on the node %s had empty join subnet IPs", nodeName) } remoteGRPortMac := util.IPAddrToHWAddr(nodeJoinSubnetIPs[0].IP) @@ -601,46 +704,40 @@ func (oc *SecondaryLayer2NetworkController) addPortForRemoteNodeGR(node *corev1. } remotePortAddr := remoteGRPortMac.String() + " " + strings.Join(remoteGRPortNetworks, " ") - klog.V(5).Infof("The remote port addresses for node %s in network %s are %s", node.Name, oc.GetNetworkName(), remotePortAddr) + klog.V(5).Infof("The remote port addresses for node %s in network %s are %s", nodeName, oc.GetNetworkName(), remotePortAddr) logicalSwitchPort := nbdb.LogicalSwitchPort{ - Name: types.SwitchToRouterPrefix + oc.GetNetworkScopedSwitchName(types.OVNLayer2Switch) + "_" + node.Name, + Name: types.SwitchToRouterPrefix + oc.GetNetworkScopedSwitchName(types.OVNLayer2Switch) + "_" + nodeName, Type: "remote", Addresses: []string{remotePortAddr}, } logicalSwitchPort.ExternalIDs = map[string]string{ types.NetworkExternalID: oc.GetNetworkName(), types.TopologyExternalID: oc.TopologyType(), - types.NodeExternalID: node.Name, + types.NodeExternalID: nodeName, } - tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, oc.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // remote node may not have the annotation yet, suppress it - return types.NewSuppressedError(err) - } - // Don't consider this node as cluster-manager has not allocated node id yet. - return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %w", - node.Name, oc.GetNetworkName(), err) + tunnelID := udnNode.Spec.Layer2TunnelID + if tunnelID == util.NoID { + return types.NewSuppressedError(fmt.Errorf("tunnel ID not set for node %s, UDN Node: %s", nodeName, udnNode.Name)) } logicalSwitchPort.Options = map[string]string{ "requested-tnl-key": strconv.Itoa(tunnelID), - "requested-chassis": node.Name, + "requested-chassis": nodeName, } sw := nbdb.LogicalSwitch{Name: oc.GetNetworkScopedSwitchName(types.OVNLayer2Switch)} - err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(oc.nbClient, &sw, &logicalSwitchPort) + err := libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(oc.nbClient, &sw, &logicalSwitchPort) if err != nil { return fmt.Errorf("failed to create port %v on logical switch %q: %v", logicalSwitchPort, sw.Name, err) } return nil } -func (oc *SecondaryLayer2NetworkController) deleteNodeEvent(node *corev1.Node) error { - if err := oc.gatewayManagerForNode(node.Name).Cleanup(); err != nil { - return fmt.Errorf("failed to cleanup gateway on node %q: %w", node.Name, err) +func (oc *SecondaryLayer2NetworkController) deleteNodeEvent(nodeName string) error { + if err := oc.gatewayManagerForNode(nodeName).Cleanup(); err != nil { + return fmt.Errorf("failed to cleanup gateway on node %q: %w", nodeName, err) } - oc.gatewayManagers.Delete(node.Name) - oc.localZoneNodes.Delete(node.Name) - oc.mgmtPortFailed.Delete(node.Name) + oc.gatewayManagers.Delete(nodeName) + oc.localZoneNodes.Delete(nodeName) + oc.mgmtPortFailed.Delete(nodeName) return nil } @@ -654,9 +751,9 @@ func (oc *SecondaryLayer2NetworkController) deleteNodeEvent(node *corev1.Node) e // externalIP = "169.254.0.12"; which is the masqueradeIP for this L2 UDN // so all in all we want to condionally SNAT all packets that are coming from pods hosted on this node, // which are leaving via UDN's mpX interface to the UDN's masqueradeIP. -func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(localPodSubnets []*net.IPNet, routerName string, node *kapi.Node) error { +func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(macAddr net.HardwareAddr, localPodSubnets []*net.IPNet, routerName, nodeName string) error { outputPort := types.GWRouterToJoinSwitchPrefix + routerName - nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort, node) + nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort, nodeName, macAddr) if err != nil { return err } @@ -680,7 +777,7 @@ type SecondaryL2GatewayConfig struct { externalIPs []net.IP } -func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) (*SecondaryL2GatewayConfig, error) { +func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(gwLRPIPs []*net.IPNet, node *corev1.Node) (*SecondaryL2GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return nil, fmt.Errorf("failed to get node %s network %s L3 gateway config: %v", node.Name, oc.GetNetworkName(), err) @@ -714,13 +811,6 @@ func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) hostSubnets = append(hostSubnets, subnet.CIDR) } - // at layer2 the GR LRP should be different per node same we do for layer3 - // since they should not collide at the distributed switch later on - gwLRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, networkName) - if err != nil { - return nil, fmt.Errorf("failed composing LRP addresses for layer2 network %s: %w", oc.GetNetworkName(), err) - } - // At layer2 GR LRP acts as the layer3 ovn_cluster_router so we need // to configure here the .1 address, this will work only for IC with // one node per zone, since ARPs for .1 will not go beyond local switch. diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 72ec4937e7..851aca4b29 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -101,9 +101,12 @@ func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface case factory.UserDefinedNodeType: udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { - return fmt.Errorf("could not cast %T object to *kapi.Node", obj) + return fmt.Errorf("could not cast %T object to *userdefinednodeapi.UDNNode", obj) } + if h.oc.GetNetworkName() != udnNode.GetLabels()["networkName"] { + return nil + } nodeName := udnNode.GetLabels()["nodeName"] if nodeName == "" { return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) @@ -242,7 +245,9 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne if !ok { return fmt.Errorf("could not cast oldObj of type %T to *userdefinednodeapi.UDNNode", oldObj) } - + if h.oc.GetNetworkName() != newUDNNode.GetLabels()["networkName"] { + return nil + } nodeName := newUDNNode.GetLabels()["nodeName"] if nodeName == "" { return fmt.Errorf("unable to find nodeName label for udn Node: %s", newUDNNode.Name) @@ -255,19 +260,19 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne newNodeIsLocalZoneNode := h.oc.isLocalZoneNode(node) // can node subnet change for UDN? - nodeSubnetChanged := udnNodeSubnetChanged(oldUDNNode, newUDNNode) + nodeSubnetChange := udnNodeSubnetChanged(oldUDNNode, newUDNNode) if newNodeIsLocalZoneNode { var nodeSyncsParam *nodeSyncs // determine what actually changed in this update _, nodeSync := h.oc.addNodeFailed.Load(node.Name) _, failed := h.oc.nodeClusterRouterPortFailed.Load(node.Name) - clusterRtrSync := failed || nodeSubnetChanged + clusterRtrSync := failed || nodeSubnetChange _, failed = h.oc.mgmtPortFailed.Load(node.Name) - syncMgmtPort := failed || udnNodeMACAddressChanged(oldUDNNode, newUDNNode) || nodeSubnetChanged + syncMgmtPort := failed || udnNodeMACAddressChanged(oldUDNNode, newUDNNode) || nodeSubnetChange _, syncZoneIC := h.oc.syncZoneICFailed.Load(node.Name) _, failed = h.oc.gatewaysFailed.Load(node.Name) - syncGw := failed || nodeSubnetChanged + syncGw := failed || nodeSubnetChange nodeSyncsParam = &nodeSyncs{ syncNode: nodeSync, syncClusterRouterPort: clusterRtrSync, @@ -282,7 +287,7 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne // Check if the node moved from local zone to remote zone and if so syncZoneIC should be set to true. // Also check if node subnet changed, so static routes are properly set - syncZoneIC = syncZoneIC || nodeSubnetChanged + syncZoneIC = syncZoneIC || nodeSubnetChange return h.oc.addUpdateRemoteNodeEvent(newUDNNode, node, syncZoneIC) } default: @@ -300,12 +305,16 @@ func (h *secondaryLayer3NetworkControllerEventHandler) DeleteResource(obj, cache if !ok { return fmt.Errorf("could not cast obj of type %T to *knet.Node", obj) } + if h.oc.GetNetworkName() != udnNode.GetLabels()["networkName"] { + return nil + } nodeName := udnNode.GetLabels()["nodeName"] if nodeName == "" { return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) } return h.oc.deleteNodeEvent(nodeName) - + case factory.NodeType: + return nil default: return h.oc.DeleteSecondaryNetworkResourceCommon(h.objType, obj, cachedObj) } diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index ae450e4b09..e1ec48e69b 100644 --- a/go-controller/pkg/util/node_annotations.go +++ b/go-controller/pkg/util/node_annotations.go @@ -907,6 +907,9 @@ func ParseNodeGatewayRouterJoinIPv4(node *kapi.Node, netName string) (net.IP, er func ParseNodeUDNGatewayRouterJoinAddrs(udnNode *userdefinednodeapi.UDNNode) ([]*net.IPNet, error) { var nets []*net.IPNet + if len(udnNode.Spec.JoinSubnets) == 0 { + return nil, types.NewSuppressedError(fmt.Errorf("join subnet missing in spec")) + } for _, subnet := range udnNode.Spec.JoinSubnets { _, ipnet, err := net.ParseCIDR(string(subnet)) if err != nil { From d2b16d8f456c73443fc10c074f61167827845653 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 19 Nov 2024 16:41:35 -0500 Subject: [PATCH 05/17] Speed up detecting change functions Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/ovn.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index 2f69642398..7a89aa9bbf 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -406,16 +406,13 @@ func (oc *DefaultNetworkController) syncNodeGateway(node *kapi.Node, hostSubnets // gatewayChanged() compares old annotations to new and returns true if something has changed. func gatewayChanged(oldNode, newNode *kapi.Node) bool { - oldL3GatewayConfig, _ := util.ParseNodeL3GatewayAnnotation(oldNode) - l3GatewayConfig, _ := util.ParseNodeL3GatewayAnnotation(newNode) - return !reflect.DeepEqual(oldL3GatewayConfig, l3GatewayConfig) + return oldNode.Annotations[util.OvnNodeL3GatewayConfig] != newNode.Annotations[util.OvnNodeL3GatewayConfig] || + oldNode.Annotations[util.OvnNodeChassisID] != newNode.Annotations[util.OvnNodeChassisID] } // hostCIDRsChanged compares old annotations to new and returns true if the something has changed. func hostCIDRsChanged(oldNode, newNode *kapi.Node) bool { - oldAddrs, _ := util.ParseNodeHostCIDRs(oldNode) - Addrs, _ := util.ParseNodeHostCIDRs(newNode) - return !oldAddrs.Equal(Addrs) + return util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) } // macAddressChanged() compares old annotations to new and returns true if something has changed. From ecb4a6293dea459019e301ec70d7ab150d838e48 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 19 Nov 2024 18:47:51 -0500 Subject: [PATCH 06/17] moar updates - Adds locking to protect node/UDNNode syncmap updates - Adds UDNNode client support to node controller for updating mac - make codegen Signed-off-by: Tim Rozet --- .../applyconfiguration/internal/internal.go | 61 ++++ .../applyconfiguration/udnnode/v1/udnnode.go | 223 +++++++++++++++ .../udnnode/v1/udnnodespec.go | 69 +++++ .../udnnode/v1/udnnodestatus.go | 56 ++++ .../v1/apis/applyconfiguration/utils.go | 47 ++++ .../v1/apis/clientset/versioned/clientset.go | 119 ++++++++ .../versioned/fake/clientset_generated.go | 121 ++++++++ .../v1/apis/clientset/versioned/fake/doc.go | 19 ++ .../apis/clientset/versioned/fake/register.go | 55 ++++ .../v1/apis/clientset/versioned/scheme/doc.go | 19 ++ .../clientset/versioned/scheme/register.go | 55 ++++ .../versioned/typed/udnnode/v1/doc.go | 19 ++ .../versioned/typed/udnnode/v1/fake/doc.go | 19 ++ .../typed/udnnode/v1/fake/fake_udnnode.go | 185 +++++++++++++ .../udnnode/v1/fake/fake_udnnode_client.go | 39 +++ .../typed/udnnode/v1/generated_expansion.go | 20 ++ .../versioned/typed/udnnode/v1/udnnode.go | 72 +++++ .../typed/udnnode/v1/udnnode_client.go | 106 +++++++ .../informers/externalversions/factory.go | 261 ++++++++++++++++++ .../informers/externalversions/generic.go | 61 ++++ .../internalinterfaces/factory_interfaces.go | 39 +++ .../externalversions/udnnode/interface.go | 45 +++ .../externalversions/udnnode/v1/interface.go | 44 +++ .../externalversions/udnnode/v1/udnnode.go | 88 ++++++ .../listers/udnnode/v1/expansion_generated.go | 22 ++ .../v1/apis/listers/udnnode/v1/udnnode.go | 47 ++++ .../crd/udnnode/v1/zz_generated.deepcopy.go | 156 +++++++++++ go-controller/pkg/kube/kube.go | 5 + .../node_network_controller_manager.go | 23 +- .../node/default_node_network_controller.go | 9 +- go-controller/pkg/node/gateway_udn.go | 65 +++-- .../node/secondary_node_network_controller.go | 2 +- .../secondary_layer2_network_controller.go | 10 +- .../secondary_layer3_network_controller.go | 13 +- go-controller/pkg/util/kube.go | 2 + 35 files changed, 2163 insertions(+), 33 deletions(-) create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/internal/internal.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnode.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodestatus.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/utils.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/clientset.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/clientset_generated.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/doc.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/register.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/doc.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/register.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/doc.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/doc.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode_client.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/generated_expansion.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode_client.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/factory.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/generic.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/interface.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/interface.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/udnnode.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/expansion_generated.go create mode 100644 go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/udnnode.go create mode 100644 go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go diff --git a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/internal/internal.go b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/internal/internal.go new file mode 100644 index 0000000000..765bec46ab --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/internal/internal.go @@ -0,0 +1,61 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package internal + +import ( + "fmt" + "sync" + + typed "sigs.k8s.io/structured-merge-diff/v4/typed" +) + +func Parser() *typed.Parser { + parserOnce.Do(func() { + var err error + parser, err = typed.NewParser(schemaYAML) + if err != nil { + panic(fmt.Sprintf("Failed to parse schema: %v", err)) + } + }) + return parser +} + +var parserOnce sync.Once +var parser *typed.Parser +var schemaYAML = typed.YAMLObject(`types: +- name: __untyped_atomic_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +- name: __untyped_deduced_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +`) diff --git a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnode.go b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnode.go new file mode 100644 index 0000000000..85c8ab0b53 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnode.go @@ -0,0 +1,223 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// UDNNodeApplyConfiguration represents a declarative configuration of the UDNNode type for use +// with apply. +type UDNNodeApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *UDNNodeSpecApplyConfiguration `json:"spec,omitempty"` + Status *UDNNodeStatusApplyConfiguration `json:"status,omitempty"` +} + +// UDNNode constructs a declarative configuration of the UDNNode type for use with +// apply. +func UDNNode(name string) *UDNNodeApplyConfiguration { + b := &UDNNodeApplyConfiguration{} + b.WithName(name) + b.WithKind("UDNNode") + b.WithAPIVersion("k8s.ovn.org/v1") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithKind(value string) *UDNNodeApplyConfiguration { + b.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithAPIVersion(value string) *UDNNodeApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithName(value string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithGenerateName(value string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithNamespace(value string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithUID(value types.UID) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithResourceVersion(value string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithGeneration(value int64) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithCreationTimestamp(value metav1.Time) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *UDNNodeApplyConfiguration) WithLabels(entries map[string]string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *UDNNodeApplyConfiguration) WithAnnotations(entries map[string]string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *UDNNodeApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.OwnerReferences = append(b.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *UDNNodeApplyConfiguration) WithFinalizers(values ...string) *UDNNodeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.Finalizers = append(b.Finalizers, values[i]) + } + return b +} + +func (b *UDNNodeApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithSpec(value *UDNNodeSpecApplyConfiguration) *UDNNodeApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *UDNNodeApplyConfiguration) WithStatus(value *UDNNodeStatusApplyConfiguration) *UDNNodeApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *UDNNodeApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.Name +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go new file mode 100644 index 0000000000..2c9097ae97 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go @@ -0,0 +1,69 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" +) + +// UDNNodeSpecApplyConfiguration represents a declarative configuration of the UDNNodeSpec type for use +// with apply. +type UDNNodeSpecApplyConfiguration struct { + NodeSubnets *v1.DualStackCIDRs `json:"nodeSubnets,omitempty"` + JoinSubnets *v1.DualStackCIDRs `json:"joinSubnets,omitempty"` + ManagementPortMACAddress *string `json:"managementPortMACAddress,omitempty"` + Layer2TunnelID *int `json:"layer2TunnelID,omitempty"` +} + +// UDNNodeSpecApplyConfiguration constructs a declarative configuration of the UDNNodeSpec type for use with +// apply. +func UDNNodeSpec() *UDNNodeSpecApplyConfiguration { + return &UDNNodeSpecApplyConfiguration{} +} + +// WithNodeSubnets sets the NodeSubnets field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NodeSubnets field is set to the value of the last call. +func (b *UDNNodeSpecApplyConfiguration) WithNodeSubnets(value v1.DualStackCIDRs) *UDNNodeSpecApplyConfiguration { + b.NodeSubnets = &value + return b +} + +// WithJoinSubnets sets the JoinSubnets field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the JoinSubnets field is set to the value of the last call. +func (b *UDNNodeSpecApplyConfiguration) WithJoinSubnets(value v1.DualStackCIDRs) *UDNNodeSpecApplyConfiguration { + b.JoinSubnets = &value + return b +} + +// WithManagementPortMACAddress sets the ManagementPortMACAddress field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagementPortMACAddress field is set to the value of the last call. +func (b *UDNNodeSpecApplyConfiguration) WithManagementPortMACAddress(value string) *UDNNodeSpecApplyConfiguration { + b.ManagementPortMACAddress = &value + return b +} + +// WithLayer2TunnelID sets the Layer2TunnelID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Layer2TunnelID field is set to the value of the last call. +func (b *UDNNodeSpecApplyConfiguration) WithLayer2TunnelID(value int) *UDNNodeSpecApplyConfiguration { + b.Layer2TunnelID = &value + return b +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodestatus.go b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodestatus.go new file mode 100644 index 0000000000..d8f5372076 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodestatus.go @@ -0,0 +1,56 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// UDNNodeStatusApplyConfiguration represents a declarative configuration of the UDNNodeStatus type for use +// with apply. +type UDNNodeStatusApplyConfiguration struct { + Status *string `json:"status,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// UDNNodeStatusApplyConfiguration constructs a declarative configuration of the UDNNodeStatus type for use with +// apply. +func UDNNodeStatus() *UDNNodeStatusApplyConfiguration { + return &UDNNodeStatusApplyConfiguration{} +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *UDNNodeStatusApplyConfiguration) WithStatus(value string) *UDNNodeStatusApplyConfiguration { + b.Status = &value + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *UDNNodeStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *UDNNodeStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/utils.go b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/utils.go new file mode 100644 index 0000000000..498f113d0c --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/utils.go @@ -0,0 +1,47 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package applyconfiguration + +import ( + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + internal "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/internal" + udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + testing "k8s.io/client-go/testing" +) + +// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no +// apply configuration type exists for the given GroupVersionKind. +func ForKind(kind schema.GroupVersionKind) interface{} { + switch kind { + // Group=k8s.ovn.org, Version=v1 + case v1.SchemeGroupVersion.WithKind("UDNNode"): + return &udnnodev1.UDNNodeApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("UDNNodeSpec"): + return &udnnodev1.UDNNodeSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("UDNNodeStatus"): + return &udnnodev1.UDNNodeStatusApplyConfiguration{} + + } + return nil +} + +func NewTypeConverter(scheme *runtime.Scheme) *testing.TypeConverter { + return &testing.TypeConverter{Scheme: scheme, TypeResolver: internal.Parser()} +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/clientset.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/clientset.go new file mode 100644 index 0000000000..4c6a66706a --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/clientset.go @@ -0,0 +1,119 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package versioned + +import ( + "fmt" + "net/http" + + k8sv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1" + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + K8sV1() k8sv1.K8sV1Interface +} + +// Clientset contains the clients for groups. +type Clientset struct { + *discovery.DiscoveryClient + k8sV1 *k8sv1.K8sV1Client +} + +// K8sV1 retrieves the K8sV1Client +func (c *Clientset) K8sV1() k8sv1.K8sV1Interface { + return c.k8sV1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error + cs.k8sV1, err = k8sv1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.k8sV1 = k8sv1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/clientset_generated.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 0000000000..a2a4a492d5 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,121 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + applyconfiguration "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration" + clientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" + k8sv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1" + fakek8sv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +// +// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. +// via --with-applyconfig). +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +// NewClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewClientset(objects ...runtime.Object) *Clientset { + o := testing.NewFieldManagedObjectTracker( + scheme, + codecs.UniversalDecoder(), + applyconfiguration.NewTypeConverter(scheme), + ) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// K8sV1 retrieves the K8sV1Client +func (c *Clientset) K8sV1() k8sv1.K8sV1Interface { + return &fakek8sv1.FakeK8sV1{Fake: &c.Fake} +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/doc.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/doc.go new file mode 100644 index 0000000000..19e0028ffb --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/doc.go @@ -0,0 +1,19 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/register.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/register.go new file mode 100644 index 0000000000..3e4a05464f --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/fake/register.go @@ -0,0 +1,55 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + k8sv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + k8sv1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/doc.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/doc.go new file mode 100644 index 0000000000..1aec4021fc --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/doc.go @@ -0,0 +1,19 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/register.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/register.go new file mode 100644 index 0000000000..9ae93a204e --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme/register.go @@ -0,0 +1,55 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package scheme + +import ( + k8sv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + k8sv1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/doc.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/doc.go new file mode 100644 index 0000000000..b22b05acdb --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/doc.go @@ -0,0 +1,19 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1 diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/doc.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/doc.go new file mode 100644 index 0000000000..422564f2d5 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/doc.go @@ -0,0 +1,19 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode.go new file mode 100644 index 0000000000..f9194af2f6 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode.go @@ -0,0 +1,185 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + json "encoding/json" + "fmt" + + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeUDNNodes implements UDNNodeInterface +type FakeUDNNodes struct { + Fake *FakeK8sV1 +} + +var udnnodesResource = v1.SchemeGroupVersion.WithResource("udnnodes") + +var udnnodesKind = v1.SchemeGroupVersion.WithKind("UDNNode") + +// Get takes name of the uDNNode, and returns the corresponding uDNNode object, and an error if there is any. +func (c *FakeUDNNodes) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.UDNNode, err error) { + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootGetActionWithOptions(udnnodesResource, name, options), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} + +// List takes label and field selectors, and returns the list of UDNNodes that match those selectors. +func (c *FakeUDNNodes) List(ctx context.Context, opts metav1.ListOptions) (result *v1.UDNNodeList, err error) { + emptyResult := &v1.UDNNodeList{} + obj, err := c.Fake. + Invokes(testing.NewRootListActionWithOptions(udnnodesResource, udnnodesKind, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1.UDNNodeList{ListMeta: obj.(*v1.UDNNodeList).ListMeta} + for _, item := range obj.(*v1.UDNNodeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested uDNNodes. +func (c *FakeUDNNodes) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchActionWithOptions(udnnodesResource, opts)) +} + +// Create takes the representation of a uDNNode and creates it. Returns the server's representation of the uDNNode, and an error, if there is any. +func (c *FakeUDNNodes) Create(ctx context.Context, uDNNode *v1.UDNNode, opts metav1.CreateOptions) (result *v1.UDNNode, err error) { + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootCreateActionWithOptions(udnnodesResource, uDNNode, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} + +// Update takes the representation of a uDNNode and updates it. Returns the server's representation of the uDNNode, and an error, if there is any. +func (c *FakeUDNNodes) Update(ctx context.Context, uDNNode *v1.UDNNode, opts metav1.UpdateOptions) (result *v1.UDNNode, err error) { + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootUpdateActionWithOptions(udnnodesResource, uDNNode, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeUDNNodes) UpdateStatus(ctx context.Context, uDNNode *v1.UDNNode, opts metav1.UpdateOptions) (result *v1.UDNNode, err error) { + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceActionWithOptions(udnnodesResource, "status", uDNNode, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} + +// Delete takes name of the uDNNode and deletes it. Returns an error if one occurs. +func (c *FakeUDNNodes) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(udnnodesResource, name, opts), &v1.UDNNode{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeUDNNodes) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + action := testing.NewRootDeleteCollectionActionWithOptions(udnnodesResource, opts, listOpts) + + _, err := c.Fake.Invokes(action, &v1.UDNNodeList{}) + return err +} + +// Patch applies the patch and returns the patched uDNNode. +func (c *FakeUDNNodes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.UDNNode, err error) { + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceActionWithOptions(udnnodesResource, name, pt, data, opts, subresources...), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} + +// Apply takes the given apply declarative configuration, applies it and returns the applied uDNNode. +func (c *FakeUDNNodes) Apply(ctx context.Context, uDNNode *udnnodev1.UDNNodeApplyConfiguration, opts metav1.ApplyOptions) (result *v1.UDNNode, err error) { + if uDNNode == nil { + return nil, fmt.Errorf("uDNNode provided to Apply must not be nil") + } + data, err := json.Marshal(uDNNode) + if err != nil { + return nil, err + } + name := uDNNode.Name + if name == nil { + return nil, fmt.Errorf("uDNNode.Name must be provided to Apply") + } + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceActionWithOptions(udnnodesResource, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} + +// ApplyStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). +func (c *FakeUDNNodes) ApplyStatus(ctx context.Context, uDNNode *udnnodev1.UDNNodeApplyConfiguration, opts metav1.ApplyOptions) (result *v1.UDNNode, err error) { + if uDNNode == nil { + return nil, fmt.Errorf("uDNNode provided to Apply must not be nil") + } + data, err := json.Marshal(uDNNode) + if err != nil { + return nil, err + } + name := uDNNode.Name + if name == nil { + return nil, fmt.Errorf("uDNNode.Name must be provided to Apply") + } + emptyResult := &v1.UDNNode{} + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceActionWithOptions(udnnodesResource, *name, types.ApplyPatchType, data, opts.ToPatchOptions(), "status"), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1.UDNNode), err +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode_client.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode_client.go new file mode 100644 index 0000000000..de48c1e0ff --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/fake/fake_udnnode_client.go @@ -0,0 +1,39 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeK8sV1 struct { + *testing.Fake +} + +func (c *FakeK8sV1) UDNNodes() v1.UDNNodeInterface { + return &FakeUDNNodes{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeK8sV1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/generated_expansion.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/generated_expansion.go new file mode 100644 index 0000000000..e0ecd710ce --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/generated_expansion.go @@ -0,0 +1,20 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +type UDNNodeExpansion interface{} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode.go new file mode 100644 index 0000000000..b43e296b5e --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode.go @@ -0,0 +1,72 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1" + scheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// UDNNodesGetter has a method to return a UDNNodeInterface. +// A group's client should implement this interface. +type UDNNodesGetter interface { + UDNNodes() UDNNodeInterface +} + +// UDNNodeInterface has methods to work with UDNNode resources. +type UDNNodeInterface interface { + Create(ctx context.Context, uDNNode *v1.UDNNode, opts metav1.CreateOptions) (*v1.UDNNode, error) + Update(ctx context.Context, uDNNode *v1.UDNNode, opts metav1.UpdateOptions) (*v1.UDNNode, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, uDNNode *v1.UDNNode, opts metav1.UpdateOptions) (*v1.UDNNode, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.UDNNode, error) + List(ctx context.Context, opts metav1.ListOptions) (*v1.UDNNodeList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.UDNNode, err error) + Apply(ctx context.Context, uDNNode *udnnodev1.UDNNodeApplyConfiguration, opts metav1.ApplyOptions) (result *v1.UDNNode, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, uDNNode *udnnodev1.UDNNodeApplyConfiguration, opts metav1.ApplyOptions) (result *v1.UDNNode, err error) + UDNNodeExpansion +} + +// uDNNodes implements UDNNodeInterface +type uDNNodes struct { + *gentype.ClientWithListAndApply[*v1.UDNNode, *v1.UDNNodeList, *udnnodev1.UDNNodeApplyConfiguration] +} + +// newUDNNodes returns a UDNNodes +func newUDNNodes(c *K8sV1Client) *uDNNodes { + return &uDNNodes{ + gentype.NewClientWithListAndApply[*v1.UDNNode, *v1.UDNNodeList, *udnnodev1.UDNNodeApplyConfiguration]( + "udnnodes", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *v1.UDNNode { return &v1.UDNNode{} }, + func() *v1.UDNNodeList { return &v1.UDNNodeList{} }), + } +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode_client.go b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode_client.go new file mode 100644 index 0000000000..be9367f6ab --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/typed/udnnode/v1/udnnode_client.go @@ -0,0 +1,106 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + "net/http" + + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type K8sV1Interface interface { + RESTClient() rest.Interface + UDNNodesGetter +} + +// K8sV1Client is used to interact with features provided by the k8s.ovn.org group. +type K8sV1Client struct { + restClient rest.Interface +} + +func (c *K8sV1Client) UDNNodes() UDNNodeInterface { + return newUDNNodes(c) +} + +// NewForConfig creates a new K8sV1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*K8sV1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new K8sV1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*K8sV1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &K8sV1Client{client}, nil +} + +// NewForConfigOrDie creates a new K8sV1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *K8sV1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new K8sV1Client for the given RESTClient. +func New(c rest.Interface) *K8sV1Client { + return &K8sV1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *K8sV1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/factory.go b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/factory.go new file mode 100644 index 0000000000..2a143b80da --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/factory.go @@ -0,0 +1,261 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + versioned "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" + internalinterfaces "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces" + udnnode "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client versioned.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + transform cache.TransformFunc + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// WithTransform sets a transform on all informers. +func WithTransform(transform cache.TransformFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.transform = transform + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.shuttingDown { + return + } + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() + f.startedInformers[informerType] = true + } + } +} + +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + informer.SetTransform(f.transform) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + + K8s() udnnode.Interface +} + +func (f *sharedInformerFactory) K8s() udnnode.Interface { + return udnnode.New(f, f.namespace, f.tweakListOptions) +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/generic.go b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/generic.go new file mode 100644 index 0000000000..e31136ee14 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/generic.go @@ -0,0 +1,61 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + "fmt" + + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=k8s.ovn.org, Version=v1 + case v1.SchemeGroupVersion.WithResource("udnnodes"): + return &genericInformer{resource: resource.GroupResource(), informer: f.K8s().V1().UDNNodes().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100644 index 0000000000..52c4d070e7 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,39 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package internalinterfaces + +import ( + time "time" + + versioned "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" +) + +// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/interface.go b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/interface.go new file mode 100644 index 0000000000..7514c6f3e4 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/interface.go @@ -0,0 +1,45 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package udnnode + +import ( + internalinterfaces "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces" + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1 provides access to shared informers for resources in V1. + V1() v1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1 returns a new v1.Interface. +func (g *group) V1() v1.Interface { + return v1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/interface.go b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/interface.go new file mode 100644 index 0000000000..c93419cdea --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/interface.go @@ -0,0 +1,44 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + internalinterfaces "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // UDNNodes returns a UDNNodeInformer. + UDNNodes() UDNNodeInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// UDNNodes returns a UDNNodeInformer. +func (v *version) UDNNodes() UDNNodeInformer { + return &uDNNodeInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/udnnode.go b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/udnnode.go new file mode 100644 index 0000000000..42e467c7f1 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1/udnnode.go @@ -0,0 +1,88 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + time "time" + + udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + versioned "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" + internalinterfaces "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/internalinterfaces" + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// UDNNodeInformer provides access to a shared informer and lister for +// UDNNodes. +type UDNNodeInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1.UDNNodeLister +} + +type uDNNodeInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewUDNNodeInformer constructs a new informer for UDNNode type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewUDNNodeInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredUDNNodeInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredUDNNodeInformer constructs a new informer for UDNNode type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredUDNNodeInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.K8sV1().UDNNodes().List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.K8sV1().UDNNodes().Watch(context.TODO(), options) + }, + }, + &udnnodev1.UDNNode{}, + resyncPeriod, + indexers, + ) +} + +func (f *uDNNodeInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredUDNNodeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *uDNNodeInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&udnnodev1.UDNNode{}, f.defaultInformer) +} + +func (f *uDNNodeInformer) Lister() v1.UDNNodeLister { + return v1.NewUDNNodeLister(f.Informer().GetIndexer()) +} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/expansion_generated.go b/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/expansion_generated.go new file mode 100644 index 0000000000..4b1c585abb --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/expansion_generated.go @@ -0,0 +1,22 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +// UDNNodeListerExpansion allows custom methods to be added to +// UDNNodeLister. +type UDNNodeListerExpansion interface{} diff --git a/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/udnnode.go b/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/udnnode.go new file mode 100644 index 0000000000..90e73d3c95 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1/udnnode.go @@ -0,0 +1,47 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/listers" + "k8s.io/client-go/tools/cache" +) + +// UDNNodeLister helps list UDNNodes. +// All objects returned here must be treated as read-only. +type UDNNodeLister interface { + // List lists all UDNNodes in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.UDNNode, err error) + // Get retrieves the UDNNode from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1.UDNNode, error) + UDNNodeListerExpansion +} + +// uDNNodeLister implements the UDNNodeLister interface. +type uDNNodeLister struct { + listers.ResourceIndexer[*v1.UDNNode] +} + +// NewUDNNodeLister returns a new UDNNodeLister. +func NewUDNNodeLister(indexer cache.Indexer) UDNNodeLister { + return &uDNNodeLister{listers.New[*v1.UDNNode](indexer, v1.Resource("udnnode"))} +} diff --git a/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..70b11d3b2f --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go @@ -0,0 +1,156 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in DualStackCIDRs) DeepCopyInto(out *DualStackCIDRs) { + { + in := &in + *out = make(DualStackCIDRs, len(*in)) + copy(*out, *in) + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DualStackCIDRs. +func (in DualStackCIDRs) DeepCopy() DualStackCIDRs { + if in == nil { + return nil + } + out := new(DualStackCIDRs) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UDNNode) DeepCopyInto(out *UDNNode) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDNNode. +func (in *UDNNode) DeepCopy() *UDNNode { + if in == nil { + return nil + } + out := new(UDNNode) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UDNNode) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UDNNodeList) DeepCopyInto(out *UDNNodeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]UDNNode, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDNNodeList. +func (in *UDNNodeList) DeepCopy() *UDNNodeList { + if in == nil { + return nil + } + out := new(UDNNodeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UDNNodeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UDNNodeSpec) DeepCopyInto(out *UDNNodeSpec) { + *out = *in + if in.NodeSubnets != nil { + in, out := &in.NodeSubnets, &out.NodeSubnets + *out = make(DualStackCIDRs, len(*in)) + copy(*out, *in) + } + if in.JoinSubnets != nil { + in, out := &in.JoinSubnets, &out.JoinSubnets + *out = make(DualStackCIDRs, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDNNodeSpec. +func (in *UDNNodeSpec) DeepCopy() *UDNNodeSpec { + if in == nil { + return nil + } + out := new(UDNNodeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UDNNodeStatus) DeepCopyInto(out *UDNNodeStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDNNodeStatus. +func (in *UDNNodeStatus) DeepCopy() *UDNNodeStatus { + if in == nil { + return nil + } + out := new(UDNNodeStatus) + in.DeepCopyInto(out) + return out +} diff --git a/go-controller/pkg/kube/kube.go b/go-controller/pkg/kube/kube.go index 7a75bf7114..37ba62301d 100644 --- a/go-controller/pkg/kube/kube.go +++ b/go-controller/pkg/kube/kube.go @@ -45,6 +45,7 @@ type InterfaceOVN interface { UpdateEgressServiceStatus(namespace, name, host string) error UpdateIPAMClaimIPs(updatedIPAMClaim *ipamclaimsapi.IPAMClaim) error CreateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) + UpdateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) } // Interface represents the exported methods for dealing with getting/setting @@ -471,3 +472,7 @@ func (k *KubeOVN) UpdateIPAMClaimIPs(updatedIPAMClaim *ipamclaimsapi.IPAMClaim) func (k *KubeOVN) CreateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) { return k.UDNNodeClient.K8sV1().UDNNodes().Create(context.TODO(), udnNode, metav1.CreateOptions{}) } + +func (k *KubeOVN) UpdateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) { + return k.UDNNodeClient.K8sV1().UDNNodes().Update(context.TODO(), udnNode, metav1.UpdateOptions{}) +} diff --git a/go-controller/pkg/network-controller-manager/node_network_controller_manager.go b/go-controller/pkg/network-controller-manager/node_network_controller_manager.go index 835bf0f382..bb920f1661 100644 --- a/go-controller/pkg/network-controller-manager/node_network_controller_manager.go +++ b/go-controller/pkg/network-controller-manager/node_network_controller_manager.go @@ -94,7 +94,8 @@ func (ncm *nodeNetworkControllerManager) getNetworkID(network util.BasicNetInfo) // newCommonNetworkControllerInfo creates and returns the base node network controller info func (ncm *nodeNetworkControllerManager) newCommonNetworkControllerInfo() *node.CommonNodeNetworkControllerInfo { - return node.NewCommonNodeNetworkControllerInfo(ncm.ovnNodeClient.KubeClient, ncm.ovnNodeClient.AdminPolicyRouteClient, ncm.watchFactory, ncm.recorder, ncm.name, ncm.routeManager) + return node.NewCommonNodeNetworkControllerInfo(ncm.ovnNodeClient.KubeClient, ncm.ovnNodeClient.AdminPolicyRouteClient, + ncm.ovnNodeClient.UserDefinedNodeClient, ncm.watchFactory, ncm.recorder, ncm.name, ncm.routeManager) } // NAD controller should be started on the node side under the following conditions: @@ -109,14 +110,18 @@ func isNodeNADControllerRequired() bool { func NewNodeNetworkControllerManager(ovnClient *util.OVNClientset, wf factory.NodeWatchFactory, name string, wg *sync.WaitGroup, eventRecorder record.EventRecorder, routeManager *routemanager.Controller) (*nodeNetworkControllerManager, error) { ncm := &nodeNetworkControllerManager{ - name: name, - ovnNodeClient: &util.OVNNodeClientset{KubeClient: ovnClient.KubeClient, AdminPolicyRouteClient: ovnClient.AdminPolicyRouteClient}, - Kube: &kube.Kube{KClient: ovnClient.KubeClient}, - watchFactory: wf, - stopChan: make(chan struct{}), - wg: wg, - recorder: eventRecorder, - routeManager: routeManager, + name: name, + ovnNodeClient: &util.OVNNodeClientset{ + KubeClient: ovnClient.KubeClient, + AdminPolicyRouteClient: ovnClient.AdminPolicyRouteClient, + UserDefinedNodeClient: ovnClient.UserDefinedNodeClient, + }, + Kube: &kube.Kube{KClient: ovnClient.KubeClient}, + watchFactory: wf, + stopChan: make(chan struct{}), + wg: wg, + recorder: eventRecorder, + routeManager: routeManager, } // need to configure OVS interfaces for Pods on secondary networks in the DPU mode diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index c420ef5b8b..98c45eb721 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + userdefinednodeclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" "net" "os" "strconv" @@ -55,6 +56,7 @@ type CommonNodeNetworkControllerInfo struct { recorder record.EventRecorder name string apbExternalRouteClient adminpolicybasedrouteclientset.Interface + udnNodeClient userdefinednodeclientset.Interface // route manager that creates and manages routes routeManager *routemanager.Controller } @@ -79,12 +81,14 @@ type BaseNodeNetworkController struct { } func newCommonNodeNetworkControllerInfo(kubeClient clientset.Interface, kube kube.Interface, apbExternalRouteClient adminpolicybasedrouteclientset.Interface, + udnNodeClient userdefinednodeclientset.Interface, wf factory.NodeWatchFactory, eventRecorder record.EventRecorder, name string, routeManager *routemanager.Controller) *CommonNodeNetworkControllerInfo { return &CommonNodeNetworkControllerInfo{ client: kubeClient, Kube: kube, apbExternalRouteClient: apbExternalRouteClient, + udnNodeClient: udnNodeClient, watchFactory: wf, name: name, recorder: eventRecorder, @@ -93,9 +97,10 @@ func newCommonNodeNetworkControllerInfo(kubeClient clientset.Interface, kube kub } // NewCommonNodeNetworkControllerInfo creates and returns the base node network controller info -func NewCommonNodeNetworkControllerInfo(kubeClient clientset.Interface, apbExternalRouteClient adminpolicybasedrouteclientset.Interface, wf factory.NodeWatchFactory, +func NewCommonNodeNetworkControllerInfo(kubeClient clientset.Interface, apbExternalRouteClient adminpolicybasedrouteclientset.Interface, + userDefinedNodeClient userdefinednodeclientset.Interface, wf factory.NodeWatchFactory, eventRecorder record.EventRecorder, name string, routeManager *routemanager.Controller) *CommonNodeNetworkControllerInfo { - return newCommonNodeNetworkControllerInfo(kubeClient, &kube.Kube{KClient: kubeClient}, apbExternalRouteClient, wf, eventRecorder, name, routeManager) + return newCommonNodeNetworkControllerInfo(kubeClient, &kube.Kube{KClient: kubeClient}, apbExternalRouteClient, userDefinedNodeClient, wf, eventRecorder, name, routeManager) } // DefaultNodeNetworkController is the object holder for utilities meant for node management of default network diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 3081f4514a..d3b4a99610 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -1,7 +1,12 @@ package node import ( + "context" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + userdefinednodeclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/retry" "net" "strings" "time" @@ -44,9 +49,10 @@ type UserDefinedNetworkGateway struct { // stores the networkID of this network networkID int // node that its programming things on - node *v1.Node - nodeLister listers.NodeLister - kubeInterface kube.Interface + node *v1.Node + nodeLister listers.NodeLister + kubeInterface kube.Interface + udnNodeInterface userdefinednodeclientset.Interface // vrf manager that creates and manages vrfs for all UDNs // used with a lock since its shared between all network controllers vrfManager *vrfmanager.Controller @@ -180,7 +186,7 @@ func setBridgeNetworkOfPorts(bridge *bridgeConfiguration, netName string) error } func NewUserDefinedNetworkGateway(netInfo util.NetInfo, networkID int, node *v1.Node, nodeLister listers.NodeLister, - kubeInterface kube.Interface, vrfManager *vrfmanager.Controller, ruleManager *iprulemanager.Controller, + kubeInterface kube.Interface, udnInterface userdefinednodeclientset.Interface, vrfManager *vrfmanager.Controller, ruleManager *iprulemanager.Controller, defaultNetworkGateway Gateway) (*UserDefinedNetworkGateway, error) { // Generate a per network conntrack mark and masquerade IPs to be used for egress traffic. var ( @@ -208,20 +214,44 @@ func NewUserDefinedNetworkGateway(netInfo util.NetInfo, networkID int, node *v1. } return &UserDefinedNetworkGateway{ - NetInfo: netInfo, - networkID: networkID, - node: node, - nodeLister: nodeLister, - kubeInterface: kubeInterface, - vrfManager: vrfManager, - masqCTMark: masqCTMark, - v4MasqIPs: v4MasqIPs, - v6MasqIPs: v6MasqIPs, - gateway: gw, - ruleManager: ruleManager, + NetInfo: netInfo, + networkID: networkID, + node: node, + nodeLister: nodeLister, + kubeInterface: kubeInterface, + udnNodeInterface: udnInterface, + vrfManager: vrfManager, + masqCTMark: masqCTMark, + v4MasqIPs: v4MasqIPs, + v6MasqIPs: v6MasqIPs, + gateway: gw, + ruleManager: ruleManager, }, nil } +func (udng *UserDefinedNetworkGateway) updateUDNNodeMAC(macAddress net.HardwareAddr) error { + resultErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + udnNodes, err := udng.watchFactory.UserDefinedNodeInformer().Informer().GetIndexer().Index("byNodeAndNetwork", fmt.Sprintf("%d-%s", udng.networkID, udng.node.Name)) + if err != nil { + return fmt.Errorf("failed when querying index for udnNode: %w", err) + } + if len(udnNodes) != 1 { + return fmt.Errorf("expected one udnNode, found %d", len(udnNodes)) + } + cnode := udnNodes[0].(*userdefinednodeapi.UDNNode) + cnode.Spec.ManagementPortMACAddress = macAddress.String() + _, err = udng.udnNodeInterface.K8sV1().UDNNodes().Update(context.TODO(), cnode, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }) + if resultErr != nil { + return fmt.Errorf("failed to update node %s annotation: %w", udng.node.Name, resultErr) + } + return nil +} + // AddNetwork will be responsible to create all plumbings // required by this UDN on the gateway side func (udng *UserDefinedNetworkGateway) AddNetwork() error { @@ -243,8 +273,9 @@ func (udng *UserDefinedNetworkGateway) AddNetwork() error { if err = udng.addUDNManagementPortIPs(mplink); err != nil { return fmt.Errorf("unable to add management port IP(s) for link %s, for network %s: %w", mplink.Attrs().Name, udng.GetNetworkName(), err) } - if err := util.UpdateNodeManagementPortMACAddressesWithRetry(udng.node, udng.nodeLister, udng.kubeInterface, macAddress, udng.GetNetworkName()); err != nil { - return fmt.Errorf("unable to update mac address annotation for node %s, for network %s, err: %w", udng.node.Name, udng.GetNetworkName(), err) + + if err = udng.updateUDNNodeMAC(macAddress); err != nil { + return err } // create the iprules for this network udnReplyIPRules, err := udng.constructUDNVRFIPRules(vrfTableId) diff --git a/go-controller/pkg/node/secondary_node_network_controller.go b/go-controller/pkg/node/secondary_node_network_controller.go index f3bf50f472..d5c94063fc 100644 --- a/go-controller/pkg/node/secondary_node_network_controller.go +++ b/go-controller/pkg/node/secondary_node_network_controller.go @@ -53,7 +53,7 @@ func NewSecondaryNodeNetworkController(cnnci *CommonNodeNetworkControllerInfo, n } snnc.gateway, err = NewUserDefinedNetworkGateway(snnc.NetInfo, networkID, node, - snnc.watchFactory.NodeCoreInformer().Lister(), snnc.Kube, vrfManager, ruleManager, defaultNetworkGateway) + snnc.watchFactory.NodeCoreInformer().Lister(), snnc.Kube, snnc.udnNodeClient, vrfManager, ruleManager, defaultNetworkGateway) if err != nil { return nil, fmt.Errorf("error creating UDN gateway for network %s: %v", netInfo.GetNetworkName(), err) } diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 6d42470fb8..13752d0162 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -119,6 +119,8 @@ func (h *secondaryLayer2NetworkControllerEventHandler) AddResource(obj interface return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, udnNode.Name) } if h.oc.isLocalZoneNode(node) { + h.oc.nodeMutex.LockKey(node.Name) + defer h.oc.nodeMutex.UnlockKey(node.Name) var nodeParams *nodeSyncs if fromRetryLoop { _, syncMgmtPort := h.oc.mgmtPortFailed.Load(node.Name) @@ -152,6 +154,7 @@ func (h *secondaryLayer2NetworkControllerEventHandler) DeleteResource(obj, cache if nodeName == "" { return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) } + h.oc.nodeMutex.Delete(nodeName) return h.oc.deleteNodeEvent(nodeName) case factory.NodeType: return nil @@ -176,6 +179,8 @@ func (h *secondaryLayer2NetworkControllerEventHandler) UpdateResource(oldObj, ne return fmt.Errorf("could not cast oldObj of type %T to *kapi.Node", oldObj) } newNodeIsLocalZoneNode := h.oc.isLocalZoneNode(newNode) + h.oc.nodeMutex.LockKey(newNode.Name) + defer h.oc.nodeMutex.UnlockKey(newNode.Name) needsResync := false if newNodeIsLocalZoneNode { if h.oc.isLocalZoneNode(oldNode) { @@ -239,7 +244,8 @@ func (h *secondaryLayer2NetworkControllerEventHandler) UpdateResource(oldObj, ne if err != nil { return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, newUDNNode.Name) } - + h.oc.nodeMutex.LockKey(node.Name) + defer h.oc.nodeMutex.UnlockKey(node.Name) if h.oc.isLocalZoneNode(node) { var nodeSyncsParam *nodeSyncs // determine what actually changed in this update and combine that with what failed previously @@ -308,6 +314,7 @@ type SecondaryLayer2NetworkController struct { BaseSecondaryLayer2NetworkController // Node-specific syncMaps used by node event handler + nodeMutex *syncmap.SyncMapComparableKey[string, bool] mgmtPortFailed sync.Map gatewaysFailed sync.Map syncZoneICFailed sync.Map @@ -387,6 +394,7 @@ func NewSecondaryLayer2NetworkController(cnci *CommonNetworkControllerInfo, netI }, }, }, + nodeMutex: syncmap.NewSyncMapComparableKey[string, bool](), mgmtPortFailed: sync.Map{}, syncZoneICFailed: sync.Map{}, gatewayManagers: sync.Map{}, diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 851aca4b29..5eaebdcc84 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -3,7 +3,6 @@ package ovn import ( "context" "fmt" - userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" "reflect" "sync" @@ -13,6 +12,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/pod" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" @@ -118,6 +118,8 @@ func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface } if h.oc.isLocalZoneNode(node) { + h.oc.nodeMutex.LockKey(node.Name) + defer h.oc.nodeMutex.UnlockKey(node.Name) var nodeParams *nodeSyncs if fromRetryLoop { _, nodeSync := h.oc.addNodeFailed.Load(node.Name) @@ -178,7 +180,8 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne // transit switch subnet change, node id, chassis id, primary if addr, l3 gateway config change determine // if we need to trigger UDN node type update needsResync := false - + h.oc.nodeMutex.LockKey(newNode.Name) + defer h.oc.nodeMutex.UnlockKey(newNode.Name) if newNodeIsLocalZoneNode && !h.oc.isLocalZoneNode(oldNode) { // this is a remote -> local transition, need full sync needsResync = true @@ -257,7 +260,8 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne if err != nil { return fmt.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, newUDNNode.Name) } - + h.oc.nodeMutex.LockKey(node.Name) + defer h.oc.nodeMutex.UnlockKey(node.Name) newNodeIsLocalZoneNode := h.oc.isLocalZoneNode(node) // can node subnet change for UDN? nodeSubnetChange := udnNodeSubnetChanged(oldUDNNode, newUDNNode) @@ -312,6 +316,7 @@ func (h *secondaryLayer3NetworkControllerEventHandler) DeleteResource(obj, cache if nodeName == "" { return fmt.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) } + h.oc.nodeMutex.Delete(nodeName) return h.oc.deleteNodeEvent(nodeName) case factory.NodeType: return nil @@ -365,6 +370,7 @@ type SecondaryLayer3NetworkController struct { BaseSecondaryNetworkController // Node-specific syncMaps used by node event handler + nodeMutex *syncmap.SyncMapComparableKey[string, bool] mgmtPortFailed sync.Map addNodeFailed sync.Map nodeClusterRouterPortFailed sync.Map @@ -444,6 +450,7 @@ func NewSecondaryLayer3NetworkController(cnci *CommonNetworkControllerInfo, netI nadController: nadController, }, }, + nodeMutex: syncmap.NewSyncMapComparableKey[string, bool](), mgmtPortFailed: sync.Map{}, addNodeFailed: sync.Map{}, nodeClusterRouterPortFailed: sync.Map{}, diff --git a/go-controller/pkg/util/kube.go b/go-controller/pkg/util/kube.go index 9cf74c6e73..23db4c3a3d 100644 --- a/go-controller/pkg/util/kube.go +++ b/go-controller/pkg/util/kube.go @@ -67,6 +67,7 @@ type OVNClientset struct { AdminPolicyRouteClient adminpolicybasedrouteclientset.Interface IPAMClaimsClient ipamclaimssclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } // OVNMasterClientset @@ -109,6 +110,7 @@ type OVNNodeClientset struct { AdminPolicyRouteClient adminpolicybasedrouteclientset.Interface NetworkAttchDefClient networkattchmentdefclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } type OVNClusterManagerClientset struct { From a939488df1b576678c06ca99d91dbf366fbea4e6 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 19 Nov 2024 22:12:12 -0500 Subject: [PATCH 07/17] rbac, crd creation fixes Signed-off-by: Tim Rozet --- contrib/kind.sh | 1 + dist/images/daemonset.sh | 1 + dist/templates/rbac-ovnkube-cluster-manager.yaml.j2 | 4 ++++ dist/templates/rbac-ovnkube-node.yaml.j2 | 4 ++++ go-controller/hack/update-codegen.sh | 2 ++ go-controller/pkg/factory/factory.go | 6 +++++- go-controller/pkg/factory/handler.go | 3 +++ go-controller/pkg/util/kube.go | 5 +++++ 8 files changed, 25 insertions(+), 1 deletion(-) diff --git a/contrib/kind.sh b/contrib/kind.sh index 76b0ac2cbc..bb49564b4a 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -946,6 +946,7 @@ install_ovn() { run_kubectl apply -f k8s.ovn.org_adminpolicybasedexternalroutes.yaml run_kubectl apply -f k8s.ovn.org_userdefinednetworks.yaml run_kubectl apply -f k8s.ovn.org_clusteruserdefinednetworks.yaml + run_kubectl apply -f k8s.ovn.org_udnnodes.yaml # NOTE: When you update vendoring versions for the ANP & BANP APIs, we must update the version of the CRD we pull from in the below URL run_kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/v0.1.5/config/crd/experimental/policy.networking.k8s.io_adminnetworkpolicies.yaml run_kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/v0.1.5/config/crd/experimental/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml diff --git a/dist/images/daemonset.sh b/dist/images/daemonset.sh index 6c34c3231e..9a4d0f1b95 100755 --- a/dist/images/daemonset.sh +++ b/dist/images/daemonset.sh @@ -1041,5 +1041,6 @@ cp ../templates/k8s.ovn.org_egressservices.yaml.j2 ${output_dir}/k8s.ovn.org_egr cp ../templates/k8s.ovn.org_adminpolicybasedexternalroutes.yaml.j2 ${output_dir}/k8s.ovn.org_adminpolicybasedexternalroutes.yaml cp ../templates/k8s.ovn.org_userdefinednetworks.yaml.j2 ${output_dir}/k8s.ovn.org_userdefinednetworks.yaml cp ../templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2 ${output_dir}/k8s.ovn.org_clusteruserdefinednetworks.yaml +cp ../templates/k8s.ovn.org_udnnodes.yaml.j2 ${output_dir}/k8s.ovn.org_udnnodes.yaml exit 0 diff --git a/dist/templates/rbac-ovnkube-cluster-manager.yaml.j2 b/dist/templates/rbac-ovnkube-cluster-manager.yaml.j2 index dcddae7a9f..dc1b406353 100644 --- a/dist/templates/rbac-ovnkube-cluster-manager.yaml.j2 +++ b/dist/templates/rbac-ovnkube-cluster-manager.yaml.j2 @@ -86,6 +86,10 @@ rules: - clusteruserdefinednetworks/status - clusteruserdefinednetworks/finalizers verbs: [ "patch", "update" ] + - apiGroups: ["k8s.ovn.org"] + resources: + - udnnodes + verbs: [ "create", "delete", "get", "list", "watch", "update", "patch"] - apiGroups: [""] resources: - events diff --git a/dist/templates/rbac-ovnkube-node.yaml.j2 b/dist/templates/rbac-ovnkube-node.yaml.j2 index 2375595343..82282525c5 100644 --- a/dist/templates/rbac-ovnkube-node.yaml.j2 +++ b/dist/templates/rbac-ovnkube-node.yaml.j2 @@ -184,6 +184,10 @@ rules: - userdefinednetworks - clusteruserdefinednetworks verbs: [ "get", "list", "watch" ] + - apiGroups: ["k8s.ovn.org"] + resources: + - udnnodes + verbs: [ "create", "delete", "get", "list", "watch", "update", "patch"] {% if ovn_enable_ovnkube_identity == "true" -%} - apiGroups: ["certificates.k8s.io"] resources: diff --git a/go-controller/hack/update-codegen.sh b/go-controller/hack/update-codegen.sh index 212d556550..9ead46643d 100755 --- a/go-controller/hack/update-codegen.sh +++ b/go-controller/hack/update-codegen.sh @@ -108,3 +108,5 @@ echo "Copying userdefinednetworks CRD" cp _output/crds/k8s.ovn.org_userdefinednetworks.yaml ../dist/templates/k8s.ovn.org_userdefinednetworks.yaml.j2 echo "Copying clusteruserdefinednetworks CRD" cp _output/crds/k8s.ovn.org_clusteruserdefinednetworks.yaml ../dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2 +echo "Copying userdefinednodes CRD" +cp _output/crds/k8s.ovn.org_udnnodes.yaml ../dist/templates/k8s.ovn.org_udnnodes.yaml.j2 diff --git a/go-controller/pkg/factory/factory.go b/go-controller/pkg/factory/factory.go index ed2387beb5..49392f8f95 100644 --- a/go-controller/pkg/factory/factory.go +++ b/go-controller/pkg/factory/factory.go @@ -71,6 +71,7 @@ import ( ipamclaimslister "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1" userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + userdefinednodescheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme" userdefinednodeinformerfactory "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions" userdefinednodeinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1" userdefinednetworkapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" @@ -775,6 +776,9 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset if err := userdefinednetworkapi.AddToScheme(userdefinednetworkscheme.Scheme); err != nil { return nil, err } + if err := userdefinednodeapi.AddToScheme(userdefinednodescheme.Scheme); err != nil { + return nil, err + } // For Services and Endpoints, pre-populate the shared Informer with one that // has a label selector excluding headless services. @@ -899,7 +903,7 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset if err != nil { return nil, err } - wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, wf.udnNodeFactory.K8s().V1().UDNNodes().Informer()) + wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, udnNodeInformer) if err != nil { return nil, err } diff --git a/go-controller/pkg/factory/handler.go b/go-controller/pkg/factory/handler.go index bcc5551f0b..31eccda309 100644 --- a/go-controller/pkg/factory/handler.go +++ b/go-controller/pkg/factory/handler.go @@ -22,6 +22,7 @@ import ( egressiplister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1/apis/listers/egressip/v1" anplister "sigs.k8s.io/network-policy-api/pkg/client/listers/apis/v1alpha1" + userdefinednodelister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1" userdefinednetworklister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/listers/userdefinednetwork/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -537,6 +538,8 @@ func newInformerLister(oType reflect.Type, sharedInformer cache.SharedIndexInfor return userdefinednetworklister.NewUserDefinedNetworkLister(sharedInformer.GetIndexer()), nil case ClusterUserDefinedNetworkType: return userdefinednetworklister.NewClusterUserDefinedNetworkLister(sharedInformer.GetIndexer()), nil + case UserDefinedNodeType: + return userdefinednodelister.NewUDNNodeLister(sharedInformer.GetIndexer()), nil } return nil, fmt.Errorf("cannot create lister from type %v", oType) diff --git a/go-controller/pkg/util/kube.go b/go-controller/pkg/util/kube.go index 23db4c3a3d..84325d86aa 100644 --- a/go-controller/pkg/util/kube.go +++ b/go-controller/pkg/util/kube.go @@ -205,6 +205,7 @@ func (cs *OVNClientset) GetClusterManagerClientset() *OVNClusterManagerClientset IPAMClaimsClient: cs.IPAMClaimsClient, OCPNetworkClient: cs.OCPNetworkClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } @@ -216,6 +217,7 @@ func (cs *OVNClientset) GetNodeClientset() *OVNNodeClientset { AdminPolicyRouteClient: cs.AdminPolicyRouteClient, NetworkAttchDefClient: cs.NetworkAttchDefClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } @@ -497,6 +499,8 @@ func NewOVNClientset(conf *config.KubernetesConfig) (*OVNClientset, error) { return nil, err } + userDefinedNodeClientSet, err := userdefinednodeclientset.NewForConfig(kconfig) + return &OVNClientset{ KubeClient: kclientset, ANPClient: anpClientset, @@ -511,6 +515,7 @@ func NewOVNClientset(conf *config.KubernetesConfig) (*OVNClientset, error) { AdminPolicyRouteClient: adminPolicyBasedRouteClientset, IPAMClaimsClient: ipamClaimsClientset, UserDefinedNetworkClient: userDefinedNetworkClientSet, + UserDefinedNodeClient: userDefinedNodeClientSet, }, nil } From dd2da99df70b193996637a09d81332f747e97a33 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Wed, 20 Nov 2024 18:02:18 -0500 Subject: [PATCH 08/17] Fixes tons of bugs, l3 now works Signed-off-by: Tim Rozet --- dist/templates/k8s.ovn.org_udnnodes.yaml.j2 | 150 ++++++++++++++++++ dist/templates/rbac-ovnkube-master.yaml.j2 | 4 + .../pkg/clustermanager/node/node_allocator.go | 4 + .../udnnode/v1/udnnodespec.go | 9 ++ go-controller/pkg/crd/udnnode/v1/types.go | 1 + .../crd/udnnode/v1/zz_generated.deepcopy.go | 5 + go-controller/pkg/factory/factory.go | 117 +++++++++++++- go-controller/pkg/factory/types.go | 5 + .../node_network_controller_manager.go | 31 +++- go-controller/pkg/node/gateway_udn.go | 27 ++-- .../node/secondary_node_network_controller.go | 4 +- go-controller/pkg/ovn/base_event_handler.go | 18 +++ .../pkg/ovn/base_network_controller.go | 19 +++ .../ovn/base_network_controller_secondary.go | 4 +- ...ase_secondary_layer2_network_controller.go | 14 ++ .../secondary_layer2_network_controller.go | 3 + .../secondary_layer3_network_controller.go | 37 +++++ .../ovn/zone_interconnect/zone_ic_handler.go | 61 +++++-- go-controller/pkg/types/const.go | 3 + go-controller/pkg/util/kube.go | 11 +- go-controller/pkg/util/node_annotations.go | 13 ++ 21 files changed, 505 insertions(+), 35 deletions(-) create mode 100644 dist/templates/k8s.ovn.org_udnnodes.yaml.j2 diff --git a/dist/templates/k8s.ovn.org_udnnodes.yaml.j2 b/dist/templates/k8s.ovn.org_udnnodes.yaml.j2 new file mode 100644 index 0000000000..15c443b3d8 --- /dev/null +++ b/dist/templates/k8s.ovn.org_udnnodes.yaml.j2 @@ -0,0 +1,150 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: udnnodes.k8s.ovn.org +spec: + group: k8s.ovn.org + names: + kind: UDNNode + listKind: UDNNodeList + plural: udnnodes + singular: udnnode + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: UDNNode holds node specific information per network + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: UDNNodeSpec defines the desired state of UDNNode + properties: + joinSubnets: + description: |- + JoinSubnets are used inside the OVN network topology. + + Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. + This field is only allowed for "Primary" network. + It is not recommended to set this field without explicit need and understanding of the OVN network topology. + When omitted, the platform will choose a reasonable default which is subject to change over time. + items: + type: string + maxItems: 2 + minItems: 1 + type: array + layer2TunnelID: + type: integer + managementPortMACAddress: + type: string + network-id: + type: integer + nodeSubnets: + description: |- + NodeSubnets are used for the pod network across the cluster. + + Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. + Given subnet is split into smaller subnets for every node. + items: + type: string + maxItems: 2 + minItems: 1 + type: array + type: object + status: + description: UDNNodeStatus defines the observed state of UDNNode + properties: + conditions: + description: An array of condition objects indicating details about + status of EgressQoS object. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + status: + description: A concise indication of whether the EgressQoS resource + is applied with success. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/dist/templates/rbac-ovnkube-master.yaml.j2 b/dist/templates/rbac-ovnkube-master.yaml.j2 index b4c5f64816..bb42b3bf17 100644 --- a/dist/templates/rbac-ovnkube-master.yaml.j2 +++ b/dist/templates/rbac-ovnkube-master.yaml.j2 @@ -106,6 +106,10 @@ rules: - adminnetworkpolicies/status - baselineadminnetworkpolicies/status verbs: [ "patch", "update" ] + - apiGroups: ["k8s.ovn.org"] + resources: + - udnnodes + verbs: [ "create", "delete", "get", "list", "watch", "update", "patch"] - apiGroups: ["k8s.ovn.org"] resources: - egressfirewalls/status diff --git a/go-controller/pkg/clustermanager/node/node_allocator.go b/go-controller/pkg/clustermanager/node/node_allocator.go index e4def50207..6914c0301a 100644 --- a/go-controller/pkg/clustermanager/node/node_allocator.go +++ b/go-controller/pkg/clustermanager/node/node_allocator.go @@ -266,6 +266,10 @@ func (na *NodeAllocator) syncUDNNodeNetworkAnnotations(node *corev1.Node) error spec.NodeSubnets = nodeSubnets } + if na.networkID > util.NoID { + spec.NetworkID = &na.networkID + } + x := &udnnodev1.UDNNode{ ObjectMeta: metav1.ObjectMeta{ Name: udnNodeName, diff --git a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go index 2c9097ae97..f0c99f2f65 100644 --- a/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go @@ -24,6 +24,7 @@ import ( // UDNNodeSpecApplyConfiguration represents a declarative configuration of the UDNNodeSpec type for use // with apply. type UDNNodeSpecApplyConfiguration struct { + NetworkID *int `json:"network-id,omitempty"` NodeSubnets *v1.DualStackCIDRs `json:"nodeSubnets,omitempty"` JoinSubnets *v1.DualStackCIDRs `json:"joinSubnets,omitempty"` ManagementPortMACAddress *string `json:"managementPortMACAddress,omitempty"` @@ -36,6 +37,14 @@ func UDNNodeSpec() *UDNNodeSpecApplyConfiguration { return &UDNNodeSpecApplyConfiguration{} } +// WithNetworkID sets the NetworkID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NetworkID field is set to the value of the last call. +func (b *UDNNodeSpecApplyConfiguration) WithNetworkID(value int) *UDNNodeSpecApplyConfiguration { + b.NetworkID = &value + return b +} + // WithNodeSubnets sets the NodeSubnets field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the NodeSubnets field is set to the value of the last call. diff --git a/go-controller/pkg/crd/udnnode/v1/types.go b/go-controller/pkg/crd/udnnode/v1/types.go index 86f45348ab..77e2952230 100644 --- a/go-controller/pkg/crd/udnnode/v1/types.go +++ b/go-controller/pkg/crd/udnnode/v1/types.go @@ -42,6 +42,7 @@ type UDNNode struct { // UDNNodeSpec defines the desired state of UDNNode type UDNNodeSpec struct { + NetworkID *int `json:"network-id,omitempty"` // NodeSubnets are used for the pod network across the cluster. // // Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. diff --git a/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go index 70b11d3b2f..f7076ece3c 100644 --- a/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go +++ b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go @@ -109,6 +109,11 @@ func (in *UDNNodeList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UDNNodeSpec) DeepCopyInto(out *UDNNodeSpec) { *out = *in + if in.NetworkID != nil { + in, out := &in.NetworkID, &out.NetworkID + *out = new(int) + **out = **in + } if in.NodeSubnets != nil { in, out := &in.NodeSubnets, &out.NodeSubnets *out = make(DualStackCIDRs, len(*in)) diff --git a/go-controller/pkg/factory/factory.go b/go-controller/pkg/factory/factory.go index 49392f8f95..1056f0f9ae 100644 --- a/go-controller/pkg/factory/factory.go +++ b/go-controller/pkg/factory/factory.go @@ -3,6 +3,7 @@ package factory import ( "context" "fmt" + "k8s.io/apimachinery/pkg/api/errors" "reflect" "sync/atomic" "time" @@ -74,6 +75,7 @@ import ( userdefinednodescheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned/scheme" userdefinednodeinformerfactory "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions" userdefinednodeinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1" + userdefinednodelister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1" userdefinednetworkapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" userdefinednetworkscheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned/scheme" userdefinednetworkapiinformerfactory "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/informers/externalversions" @@ -416,6 +418,27 @@ func NewOVNKubeControllerWatchFactory(ovnClientset *util.OVNKubeControllerClient if err != nil { return nil, err } + + wf.udnNodeFactory = userdefinednodeinformerfactory.NewSharedInformerFactory(ovnClientset.UserDefinedNodeClient, resyncInterval) + udnNodeInformer := wf.udnNodeFactory.K8s().V1().UDNNodes().Informer() + + indexers := cache.Indexers{ + types.UDNIndexer: func(obj interface{}) ([]string, error) { + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) + if !ok { + return nil, fmt.Errorf("unexpected UDN node type: %#v", obj) + } + return []string{util.GetUDNNodeFormat(udnNode.Labels["nodeName"], udnNode.Labels["networkName"])}, nil + }, + } + err := udnNodeInformer.AddIndexers(indexers) + if err != nil { + return nil, err + } + wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, udnNodeInformer) + if err != nil { + return nil, err + } } if util.IsMultiNetworkPoliciesSupportEnabled() { @@ -546,6 +569,15 @@ func (wf *WatchFactory) Start() error { } } + if util.IsNetworkSegmentationSupportEnabled() && wf.udnNodeFactory != nil { + wf.udnNodeFactory.Start(wf.stopChan) + for oType, synced := range waitForCacheSyncWithTimeout(wf.udnNodeFactory, wf.stopChan) { + if !synced { + return fmt.Errorf("error in syncing cache for %v informer", oType) + } + } + } + return nil } @@ -587,6 +619,10 @@ func (wf *WatchFactory) Stop() { if wf.udnFactory != nil { wf.udnFactory.Shutdown() } + + if wf.udnNodeFactory != nil { + wf.udnNodeFactory.Shutdown() + } } // NewNodeWatchFactory initializes a watch factory with significantly fewer @@ -732,6 +768,27 @@ func NewNodeWatchFactory(ovnClientset *util.OVNNodeClientset, nodeName string) ( if err != nil { return nil, err } + + wf.udnNodeFactory = userdefinednodeinformerfactory.NewSharedInformerFactory(ovnClientset.UserDefinedNodeClient, resyncInterval) + udnNodeInformer := wf.udnNodeFactory.K8s().V1().UDNNodes().Informer() + + indexers := cache.Indexers{ + types.UDNIndexer: func(obj interface{}) ([]string, error) { + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) + if !ok { + return nil, fmt.Errorf("unexpected type") + } + return []string{util.GetUDNNodeFormat(udnNode.Labels["nodeName"], udnNode.Labels["networkName"])}, nil + }, + } + err := udnNodeInformer.AddIndexers(indexers) + if err != nil { + return nil, err + } + wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, udnNodeInformer) + if err != nil { + return nil, err + } } return wf, nil @@ -891,12 +948,12 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset udnNodeInformer := wf.udnNodeFactory.K8s().V1().UDNNodes().Informer() indexers := cache.Indexers{ - "byNodeAndNetwork": func(obj interface{}) ([]string, error) { + types.UDNIndexer: func(obj interface{}) ([]string, error) { udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { return nil, fmt.Errorf("unexpected type") } - return []string{udnNode.Labels["nodeName"] + "-" + udnNode.Labels["networkName"]}, nil + return []string{util.GetUDNNodeFormat(udnNode.Labels["nodeName"], udnNode.Labels["networkName"])}, nil }, } err := udnNodeInformer.AddIndexers(indexers) @@ -988,6 +1045,10 @@ func getObjectMeta(objType reflect.Type, obj interface{}) (*metav1.ObjectMeta, e if persistentips, ok := obj.(*ipamclaimsapi.IPAMClaim); ok { return &persistentips.ObjectMeta, nil } + case UserDefinedNodeType: + if udnNode, ok := obj.(*userdefinednodeapi.UDNNode); ok { + return &udnNode.ObjectMeta, nil + } } return nil, fmt.Errorf("cannot get ObjectMeta from type %v", objType) @@ -1099,7 +1160,14 @@ func (wf *WatchFactory) GetResourceHandlerFunc(objType reflect.Type) (AddHandler funcs cache.ResourceEventHandler, processExisting func([]interface{}) error) (*Handler, error) { return wf.AddIPAMClaimsHandler(funcs, processExisting) }, nil + + case UserDefinedNodeType: + return func(namespace string, sel labels.Selector, + funcs cache.ResourceEventHandler, processExisting func([]interface{}) error) (*Handler, error) { + return wf.AddFilteredUDNNodeHandler(sel, funcs, processExisting) + }, nil } + return nil, fmt.Errorf("cannot get ObjectMeta from type %v", objType) } @@ -1319,11 +1387,21 @@ func (wf *WatchFactory) AddFilteredNodeHandler(sel labels.Selector, handlerFuncs return wf.addHandler(NodeType, "", sel, handlerFuncs, processExisting, defaultHandlerPriority) } +// AddFilteredUDNNodeHandler adds a handler function that will be executed when UDNNode objects that match the given label selector +func (wf *WatchFactory) AddFilteredUDNNodeHandler(sel labels.Selector, handlerFuncs cache.ResourceEventHandler, processExisting func([]interface{}) error) (*Handler, error) { + return wf.addHandler(UserDefinedNodeType, "", sel, handlerFuncs, processExisting, defaultHandlerPriority) +} + // RemoveNodeHandler removes a Node object event handler function func (wf *WatchFactory) RemoveNodeHandler(handler *Handler) { wf.removeHandler(NodeType, handler) } +// RemoveUDNNodeHandler removes a Node object event handler function +func (wf *WatchFactory) RemoveUDNNodeHandler(handler *Handler) { + wf.removeHandler(UserDefinedNodeType, handler) +} + // GetPod returns the pod spec given the namespace and pod name func (wf *WatchFactory) GetPod(namespace, name string) (*kapi.Pod, error) { podLister := wf.informers[PodType].lister.(listers.PodLister) @@ -1499,6 +1577,41 @@ func (wf *WatchFactory) GetNADs(namespace string) ([]*nadapi.NetworkAttachmentDe return nadLister.NetworkAttachmentDefinitions(namespace).List(labels.Everything()) } +func (wf *WatchFactory) GetUDNNode(udnNodeName string) (*userdefinednodeapi.UDNNode, error) { + udnNodeLister := wf.informers[UserDefinedNodeType].lister.(userdefinednodelister.UDNNodeLister) + return udnNodeLister.Get(udnNodeName) +} + +// GetUDNNodeByLabels retrieves corresponding UDN Node object by node name +func (wf *WatchFactory) GetUDNNodeByLabels(nodeName, networkName string) (*userdefinednodeapi.UDNNode, error) { + u := &userdefinednodeapi.UDNNode{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "nodeName": nodeName, + "networkName": networkName, + }, + }, + } + udnNodes, err := wf.UserDefinedNodeInformer().Informer().GetIndexer().Index( + types.UDNIndexer, u) + if err != nil { + return nil, err + } + if len(udnNodes) == 0 { + return nil, errors.NewNotFound(userdefinednodeapi.Resource("udnnode"), util.GetUDNNodeFormat(nodeName, networkName)) + } + if len(udnNodes) > 1 { + panic(fmt.Sprintf("should never have more than one UDN node of a specific index: %s, %d", util.GetUDNNodeFormat(nodeName, networkName), len(udnNodes))) + } + return udnNodes[0].(*userdefinednodeapi.UDNNode), nil +} + +func (wf *WatchFactory) GetUDNNodes(networkName string) ([]*userdefinednodeapi.UDNNode, error) { + udnNodeLister := wf.informers[UserDefinedNodeType].lister.(userdefinednodelister.UDNNodeLister) + selector := labels.Set{"networkName": networkName}.AsSelector() + return udnNodeLister.List(selector) +} + func (wf *WatchFactory) NodeInformer() cache.SharedIndexInformer { return wf.informers[NodeType].inf } diff --git a/go-controller/pkg/factory/types.go b/go-controller/pkg/factory/types.go index 637279c343..dee96135e2 100644 --- a/go-controller/pkg/factory/types.go +++ b/go-controller/pkg/factory/types.go @@ -3,6 +3,7 @@ package factory import ( adminpolicybasedrouteinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/informers/externalversions/adminpolicybasedroute/v1" egressipinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1/apis/informers/externalversions/egressip/v1" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" userdefinednetworkinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/informers/externalversions/userdefinednetwork/v1" nadinformer "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/informers/externalversions/k8s.cni.cncf.io/v1" @@ -77,6 +78,10 @@ type NodeWatchFactory interface { GetServiceEndpointSlices(namespace, svcName, network string) ([]*discovery.EndpointSlice, error) GetNamespace(name string) (*kapi.Namespace, error) + + GetUDNNode(udnNodeName string) (*userdefinednodeapi.UDNNode, error) + GetUDNNodes(networkName string) ([]*userdefinednodeapi.UDNNode, error) + GetUDNNodeByLabels(nodeName, networkName string) (*userdefinednodeapi.UDNNode, error) } type Shutdownable interface { diff --git a/go-controller/pkg/network-controller-manager/node_network_controller_manager.go b/go-controller/pkg/network-controller-manager/node_network_controller_manager.go index bb920f1661..b4430350f0 100644 --- a/go-controller/pkg/network-controller-manager/node_network_controller_manager.go +++ b/go-controller/pkg/network-controller-manager/node_network_controller_manager.go @@ -3,6 +3,8 @@ package networkControllerManager import ( "context" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + kapi "k8s.io/api/core/v1" "strings" "sync" "time" @@ -81,13 +83,28 @@ func (ncm *nodeNetworkControllerManager) CleanupDeletedNetworks(validNetworks .. } func (ncm *nodeNetworkControllerManager) getNetworkID(network util.BasicNetInfo) (int, error) { - nodes, err := ncm.watchFactory.GetNodes() - if err != nil { - return util.InvalidID, err - } - networkID, err := util.GetNetworkID(nodes, network) - if err != nil { - return util.InvalidID, err + var err error + var networkID int + if network.IsDefault() { + var nodes []*kapi.Node + nodes, err = ncm.watchFactory.GetNodes() + if err != nil { + return util.InvalidID, err + } + networkID, err = util.GetNetworkID(nodes, network) + if err != nil { + return util.InvalidID, err + } + } else { + var udnNodes []*userdefinednodeapi.UDNNode + udnNodes, err = ncm.watchFactory.GetUDNNodes(network.GetNetworkName()) + if err != nil { + return util.InvalidID, err + } + networkID, err = util.GetUDNNetworkID(udnNodes, network.GetNetworkName()) + if err != nil || networkID <= util.NoID { + return util.InvalidID, err + } } return networkID, nil } diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index d3b4a99610..c7025392e3 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -3,7 +3,6 @@ package node import ( "context" "fmt" - userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" userdefinednodeclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/clientset/versioned" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/retry" @@ -231,16 +230,13 @@ func NewUserDefinedNetworkGateway(netInfo util.NetInfo, networkID int, node *v1. func (udng *UserDefinedNetworkGateway) updateUDNNodeMAC(macAddress net.HardwareAddr) error { resultErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - udnNodes, err := udng.watchFactory.UserDefinedNodeInformer().Informer().GetIndexer().Index("byNodeAndNetwork", fmt.Sprintf("%d-%s", udng.networkID, udng.node.Name)) + udnNode, err := udng.watchFactory.GetUDNNodeByLabels(udng.node.Name, udng.GetNetworkName()) if err != nil { - return fmt.Errorf("failed when querying index for udnNode: %w", err) - } - if len(udnNodes) != 1 { - return fmt.Errorf("expected one udnNode, found %d", len(udnNodes)) + return err } - cnode := udnNodes[0].(*userdefinednodeapi.UDNNode) + cnode := *udnNode cnode.Spec.ManagementPortMACAddress = macAddress.String() - _, err = udng.udnNodeInterface.K8sV1().UDNNodes().Update(context.TODO(), cnode, metav1.UpdateOptions{}) + _, err = udng.udnNodeInterface.K8sV1().UDNNodes().Update(context.TODO(), &cnode, metav1.UpdateOptions{}) if err != nil { return err } @@ -408,15 +404,26 @@ func (udng *UserDefinedNetworkGateway) addUDNManagementPort() (netlink.Link, net } func (udng *UserDefinedNetworkGateway) addUDNManagementPortIPs(mpLink netlink.Link) error { - var err error var networkLocalSubnets []*net.IPNet // fetch subnets which we will use to get management port IP(s) if udng.TopologyType() == types.Layer3Topology { - networkLocalSubnets, err = util.ParseNodeHostSubnetAnnotation(udng.node, udng.GetNetworkName()) + udnNode, err := udng.watchFactory.GetUDNNodeByLabels(udng.node.Name, udng.GetNetworkName()) if err != nil { return fmt.Errorf("waiting for node %s to start, no annotation found on node for network %s: %w", udng.node.Name, udng.GetNetworkName(), err) } + if len(udnNode.Spec.NodeSubnets) == 0 { + return fmt.Errorf("subnets are empty for UDN Node: %s, for node: %s, network %s", + udnNode.Name, udng.node.Name, udng.GetNetworkName()) + } + for _, subnet := range udnNode.Spec.NodeSubnets { + _, n, err := net.ParseCIDR(string(subnet)) + if err != nil { + return fmt.Errorf("failed to parse CIDR %q for node %s, network %s: %w", + subnet, udng.node.Name, udng.GetNetworkName(), err) + } + networkLocalSubnets = append(networkLocalSubnets, n) + } } else if udng.TopologyType() == types.Layer2Topology { // NOTE: We don't support L2 networks without subnets as primary UDNs globalFlatL2Networks := udng.Subnets() diff --git a/go-controller/pkg/node/secondary_node_network_controller.go b/go-controller/pkg/node/secondary_node_network_controller.go index d5c94063fc..db18e0d763 100644 --- a/go-controller/pkg/node/secondary_node_network_controller.go +++ b/go-controller/pkg/node/secondary_node_network_controller.go @@ -104,11 +104,11 @@ func (nc *SecondaryNodeNetworkController) Cleanup() error { func (oc *SecondaryNodeNetworkController) getNetworkID() (int, error) { if oc.networkID == nil || *oc.networkID == util.InvalidID { oc.networkID = ptr.To(util.InvalidID) - nodes, err := oc.watchFactory.GetNodes() + udnNodes, err := oc.watchFactory.GetUDNNodes(oc.GetNetworkName()) if err != nil { return util.InvalidID, err } - *oc.networkID, err = util.GetNetworkID(nodes, oc.NetInfo) + *oc.networkID, err = util.GetUDNNetworkID(udnNodes, oc.GetNetworkName()) if err != nil { return util.InvalidID, err } diff --git a/go-controller/pkg/ovn/base_event_handler.go b/go-controller/pkg/ovn/base_event_handler.go index 5422a4dd1b..92ca5e03f0 100644 --- a/go-controller/pkg/ovn/base_event_handler.go +++ b/go-controller/pkg/ovn/base_event_handler.go @@ -2,6 +2,7 @@ package ovn import ( "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "reflect" kapi "k8s.io/api/core/v1" @@ -26,6 +27,7 @@ func hasResourceAnUpdateFunc(objType reflect.Type) bool { switch objType { case factory.PodType, factory.NodeType, + factory.UserDefinedNodeType, factory.EgressIPType, factory.EgressIPNamespaceType, factory.EgressIPPodType, @@ -73,6 +75,19 @@ func (h *baseNetworkControllerEventHandler) areResourcesEqual(objType reflect.Ty } return !shouldUpdate, nil + case factory.UserDefinedNodeType: + // TODO(trozet): double check this later + udnNode1, ok := obj1.(*userdefinednodeapi.UDNNode) + if !ok { + return false, fmt.Errorf("could not cast obj1 of type %T to *userdefinednodeapi.UDNNode", obj1) + } + udnNode2, ok := obj2.(*userdefinednodeapi.UDNNode) + if !ok { + return false, fmt.Errorf("could not cast obj2 of type %T to *userdefinednodeapi.UDNNode", obj2) + } + + return reflect.DeepEqual(udnNode1.Spec, udnNode2.Spec), nil + case factory.PodType, factory.EgressIPPodType: // For these types, there was no old vs new obj comparison in the original update code, @@ -167,6 +182,9 @@ func (h *baseNetworkControllerEventHandler) getResourceFromInformerCache(objType case factory.IPAMClaimsType: obj, err = watchFactory.GetIPAMClaim(namespace, name) + case factory.UserDefinedNodeType: + obj, err = watchFactory.GetUDNNode(name) + default: err = fmt.Errorf("object type %s not supported, cannot retrieve it from informers cache", objType) diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index 262f9c1a8c..1bf77b70c4 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -3,6 +3,7 @@ package ovn import ( "fmt" userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + "k8s.io/apimachinery/pkg/labels" "net" "reflect" "sync" @@ -109,6 +110,8 @@ type BaseNetworkController struct { namespaceHandler *factory.Handler // ipam claims events factory Handler ipamClaimsHandler *factory.Handler + // udnNodeHandler + udnNodeHandler *factory.Handler // A cache of all logical switches seen by the watcher and their subnets lsManager *lsm.LogicalSwitchManager @@ -700,6 +703,22 @@ func (bnc *BaseNetworkController) syncNodeManagementPort(macAddress net.Hardware return mgmtPortIPs, nil } +// WatchUDNNodes starts the watching of node resource and calls +// back the appropriate handler logic +func (oc *BaseNetworkController) WatchUDNNodes() error { + if oc.udnNodeHandler != nil { + return nil + } + selector := labels.SelectorFromSet(labels.Set{ + "networkName": oc.GetNetworkName(), + }) + handler, err := oc.retryUDNNodes.WatchResourceFiltered("", selector) + if err == nil { + oc.udnNodeHandler = handler + } + return err +} + // WatchNodes starts the watching of the nodes resource and calls back the appropriate handler logic func (bnc *BaseNetworkController) WatchNodes() error { if bnc.nodeHandler != nil { diff --git a/go-controller/pkg/ovn/base_network_controller_secondary.go b/go-controller/pkg/ovn/base_network_controller_secondary.go index 5da4002879..f5df02ce9c 100644 --- a/go-controller/pkg/ovn/base_network_controller_secondary.go +++ b/go-controller/pkg/ovn/base_network_controller_secondary.go @@ -791,11 +791,11 @@ func (oc *BaseSecondaryNetworkController) allowPersistentIPs() bool { func (oc *BaseSecondaryNetworkController) getNetworkID() (int, error) { if oc.networkID == nil || *oc.networkID == util.InvalidID { oc.networkID = ptr.To(util.InvalidID) - nodes, err := oc.watchFactory.GetNodes() + udnNodes, err := oc.watchFactory.GetUDNNodes(oc.GetNetworkName()) if err != nil { return util.InvalidID, err } - *oc.networkID, err = util.GetNetworkID(nodes, oc.NetInfo) + *oc.networkID, err = util.GetUDNNetworkID(udnNodes, oc.GetNetworkName()) if err != nil { return util.InvalidID, err } diff --git a/go-controller/pkg/ovn/base_secondary_layer2_network_controller.go b/go-controller/pkg/ovn/base_secondary_layer2_network_controller.go index 71d6df0849..e903b5644b 100644 --- a/go-controller/pkg/ovn/base_secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/base_secondary_layer2_network_controller.go @@ -49,6 +49,9 @@ func (oc *BaseSecondaryLayer2NetworkController) stop() { if oc.namespaceHandler != nil { oc.watchFactory.RemoveNamespaceHandler(oc.namespaceHandler) } + if oc.udnNodeHandler != nil { + oc.watchFactory.RemoveUDNNodeHandler(oc.udnNodeHandler) + } } // cleanup cleans up logical entities for the given network, called from net-attach-def routine @@ -89,6 +92,12 @@ func (oc *BaseSecondaryLayer2NetworkController) run() error { return err } + if config.OVNKubernetesFeature.EnableNetworkSegmentation { + if err := oc.WatchUDNNodes(); err != nil { + return err + } + } + // when on IC, it will be the NetworkController that returns the IPAMClaims // IPs back to the pool if oc.allocatesPodAnnotation() && oc.allowPersistentIPs() { @@ -209,6 +218,11 @@ func (oc *BaseSecondaryLayer2NetworkController) deleteNodeEvent(node *corev1.Nod return nil } +func (oc *BaseSecondaryLayer2NetworkController) syncUDNNodes(nodes []interface{}) error { + // TODO(trozet): implement + return nil +} + func (oc *BaseSecondaryLayer2NetworkController) syncNodes(nodes []interface{}) error { for _, tmp := range nodes { node, ok := tmp.(*corev1.Node) diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 13752d0162..dd5923d29d 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -292,6 +292,9 @@ func (h *secondaryLayer2NetworkControllerEventHandler) SyncFunc(objs []interface case factory.IPAMClaimsType: syncFunc = h.oc.syncIPAMClaims + case factory.UserDefinedNodeType: + syncFunc = h.oc.syncUDNNodes + default: return fmt.Errorf("no sync function for object type %s", h.objType) } diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 5eaebdcc84..3378eb43ca 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -3,6 +3,7 @@ package ovn import ( "context" "fmt" + "k8s.io/apimachinery/pkg/labels" "net" "reflect" "sync" @@ -98,6 +99,9 @@ func (h *secondaryLayer3NetworkControllerEventHandler) IsResourceScheduled(obj i // Given an object to add and a boolean specifying if the function was executed from iterateRetryResources func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface{}, fromRetryLoop bool) error { switch h.objType { + case factory.NodeType: + //do nothing + return nil case factory.UserDefinedNodeType: udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { @@ -348,6 +352,9 @@ func (h *secondaryLayer3NetworkControllerEventHandler) SyncFunc(objs []interface case factory.MultiNetworkPolicyType: syncFunc = h.oc.syncMultiNetworkPolicies + case factory.UserDefinedNodeType: + syncFunc = h.oc.syncUDNNodes + default: return fmt.Errorf("no sync function for object type %s", h.objType) } @@ -557,6 +564,9 @@ func (oc *SecondaryLayer3NetworkController) Stop() { if oc.nodeHandler != nil { oc.watchFactory.RemoveNodeHandler(oc.nodeHandler) } + if oc.udnNodeHandler != nil { + oc.watchFactory.RemoveUDNNodeHandler(oc.udnNodeHandler) + } if oc.namespaceHandler != nil { oc.watchFactory.RemoveNamespaceHandler(oc.namespaceHandler) } @@ -638,6 +648,12 @@ func (oc *SecondaryLayer3NetworkController) Run() error { return err } + if config.OVNKubernetesFeature.EnableNetworkSegmentation { + if err := oc.WatchUDNNodes(); err != nil { + return err + } + } + if oc.svcController != nil { startSvc := time.Now() // Services should be started after nodes to prevent LB churn @@ -673,6 +689,22 @@ func (oc *SecondaryLayer3NetworkController) Run() error { return nil } +// WatchUDNNodes starts the watching of node resource and calls +// back the appropriate handler logic +func (oc *SecondaryLayer3NetworkController) WatchUDNNodes() error { + if oc.udnNodeHandler != nil { + return nil + } + selector := labels.SelectorFromSet(labels.Set{ + "networkName": oc.GetNetworkName(), + }) + handler, err := oc.retryUDNNodes.WatchResourceFiltered("", selector) + if err == nil { + oc.udnNodeHandler = handler + } + return err +} + // WatchNodes starts the watching of node resource and calls // back the appropriate handler logic func (oc *SecondaryLayer3NetworkController) WatchNodes() error { @@ -1003,6 +1035,11 @@ func (oc *SecondaryLayer3NetworkController) deleteNode(nodeName string) error { return nil } +func (oc *SecondaryLayer3NetworkController) syncUDNNodes(nodes []interface{}) error { + //TODO(trozet) implement + return nil +} + // We only deal with cleaning up nodes that shouldn't exist here, since // watchNodes() will be called for all existing nodes at startup anyway. // Note that this list will include the 'join' cluster switch, which we diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index 0f5071cc42..d104444495 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" "net" "strconv" "time" @@ -180,21 +181,34 @@ func (zic *ZoneInterconnectHandler) ensureTransitSwitch(nodes []*corev1.Node) er } start := time.Now() - // first try to get the network ID from the current state of the nodes - networkID, err := zic.getNetworkIdFromNodes(nodes) - - // if not set yet, let's retry for a bit - if util.IsAnnotationNotSetError(err) { - maxTimeout := 2 * time.Minute + var networkID int + var err error + maxTimeout := 2 * time.Minute + if zic.IsDefault() { + // first try to get the network ID from the current state of the nodes + networkID, err = zic.getNetworkIdFromNodes(nodes) + // if not set yet, let's retry for a bit + if util.IsAnnotationNotSetError(err) { + err = wait.PollUntilContextTimeout(context.Background(), 250*time.Millisecond, maxTimeout, true, func(ctx context.Context) (bool, error) { + var err error + networkID, err = zic.getNetworkId() + if util.IsAnnotationNotSetError(err) { + return false, nil + } + if err != nil { + return false, err + } + + return true, nil + }) + } + } else { err = wait.PollUntilContextTimeout(context.Background(), 250*time.Millisecond, maxTimeout, true, func(ctx context.Context) (bool, error) { var err error - networkID, err = zic.getNetworkId() - if util.IsAnnotationNotSetError(err) { + networkID, err = zic.getNetworkIDFromUDNNodes() + if err != nil || networkID <= util.NoID { return false, nil } - if err != nil { - return false, err - } return true, nil }) @@ -725,3 +739,28 @@ func (zic *ZoneInterconnectHandler) getNetworkIdFromNodes(nodes []*corev1.Node) return util.InvalidID, fmt.Errorf("could not find network ID: %w", err) } + +func (zic *ZoneInterconnectHandler) getNetworkIDFromUDNNodes() (int, error) { + nodes, err := zic.watchFactory.GetUDNNodes(zic.GetNetworkName()) + if err != nil { + return util.InvalidID, err + } + return zic.updateNetworkIDFromUDNNodes(nodes) +} + +// getNetworkIDFromUDNNodes returns the cached network ID or looks it up in any of the provided nodes +func (zic *ZoneInterconnectHandler) updateNetworkIDFromUDNNodes(nodes []*userdefinednodeapi.UDNNode) (int, error) { + if zic.networkId != util.InvalidID { + return zic.networkId, nil + } + + var err error + for _, i := range nodes { + if i.Spec.NetworkID != nil && *i.Spec.NetworkID > util.NoID { + zic.networkId = *i.Spec.NetworkID + return zic.networkId, nil + } + } + + return util.InvalidID, fmt.Errorf("could not find network ID: %w", err) +} diff --git a/go-controller/pkg/types/const.go b/go-controller/pkg/types/const.go index d014dc3a3b..e4d676bf4a 100644 --- a/go-controller/pkg/types/const.go +++ b/go-controller/pkg/types/const.go @@ -247,4 +247,7 @@ const ( // entry for the gateway routers. After this time, the entry is removed and // may be refreshed with a new ARP request. GRMACBindingAgeThreshold = "300" + + // UDNIndexer is the name of the label indexer used for UDN Node informer + UDNIndexer = "byNodeAndNetwork" ) diff --git a/go-controller/pkg/util/kube.go b/go-controller/pkg/util/kube.go index 84325d86aa..d35d3cee15 100644 --- a/go-controller/pkg/util/kube.go +++ b/go-controller/pkg/util/kube.go @@ -85,9 +85,9 @@ type OVNMasterClientset struct { IPAMClaimsClient ipamclaimssclientset.Interface NetworkAttchDefClient networkattchmentdefclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } -// OVNNetworkControllerManagerClientset type OVNKubeControllerClientset struct { KubeClient kubernetes.Interface ANPClient anpclientset.Interface @@ -101,6 +101,7 @@ type OVNKubeControllerClientset struct { IPAMClaimsClient ipamclaimssclientset.Interface NetworkAttchDefClient networkattchmentdefclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } type OVNNodeClientset struct { @@ -139,6 +140,7 @@ var ( certUsages = []certificatesv1.KeyUsage{certificatesv1.UsageDigitalSignature, certificatesv1.UsageClientAuth} ) +// GetMasterClientset is for runMode.clusterManager && runMode.ovnkubeController func (cs *OVNClientset) GetMasterClientset() *OVNMasterClientset { return &OVNMasterClientset{ KubeClient: cs.KubeClient, @@ -157,6 +159,10 @@ func (cs *OVNClientset) GetMasterClientset() *OVNMasterClientset { } } +// GetOVNKubeControllerClientset for +// a) ovnkube controller + cluster manager or +// b) ovnkube controller + node +// c) all-in-one a.k.a ovnkube controller + cluster-manager + node func (cs *OVNMasterClientset) GetOVNKubeControllerClientset() *OVNKubeControllerClientset { return &OVNKubeControllerClientset{ KubeClient: cs.KubeClient, @@ -171,9 +177,11 @@ func (cs *OVNMasterClientset) GetOVNKubeControllerClientset() *OVNKubeController IPAMClaimsClient: cs.IPAMClaimsClient, NetworkAttchDefClient: cs.NetworkAttchDefClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } +// GetOVNKubeControllerClientset for runMode.ovnkubeController func (cs *OVNClientset) GetOVNKubeControllerClientset() *OVNKubeControllerClientset { return &OVNKubeControllerClientset{ KubeClient: cs.KubeClient, @@ -188,6 +196,7 @@ func (cs *OVNClientset) GetOVNKubeControllerClientset() *OVNKubeControllerClient IPAMClaimsClient: cs.IPAMClaimsClient, NetworkAttchDefClient: cs.NetworkAttchDefClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index e1ec48e69b..54be6b7e6e 100644 --- a/go-controller/pkg/util/node_annotations.go +++ b/go-controller/pkg/util/node_annotations.go @@ -1515,3 +1515,16 @@ func GetNetworkID(nodes []*corev1.Node, nInfo BasicNetInfo) (int, error) { } return InvalidID, fmt.Errorf("missing network id for network '%s'", nInfo.GetNetworkName()) } + +func GetUDNNetworkID(udnNodes []*userdefinednodeapi.UDNNode, networkName string) (int, error) { + for _, udnNode := range udnNodes { + if udnNode.Spec.NetworkID != nil && *udnNode.Spec.NetworkID > 0 { + return *udnNode.Spec.NetworkID, nil + } + } + return InvalidID, fmt.Errorf("missing network id for network '%s'", networkName) +} + +func GetUDNNodeFormat(nodeName, networkName string) string { + return fmt.Sprintf("%s-%s", networkName, nodeName) +} From f8af57e65cd1864d175eef3c532ebc13ce1c9876 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Wed, 20 Nov 2024 23:10:21 -0500 Subject: [PATCH 09/17] Layer 3 delete now works, layer 2 almost working Signed-off-by: Tim Rozet --- .../network_cluster_controller.go | 36 +++++------ .../pkg/clustermanager/node/node_allocator.go | 60 ++++++++++++------- go-controller/pkg/crd/udnnode/v1/types.go | 2 +- .../crd/udnnode/v1/zz_generated.deepcopy.go | 5 ++ go-controller/pkg/kube/kube.go | 5 ++ .../secondary_layer2_network_controller.go | 11 +++- .../ovn/zone_interconnect/zone_ic_handler.go | 13 +++- 7 files changed, 86 insertions(+), 46 deletions(-) diff --git a/go-controller/pkg/clustermanager/network_cluster_controller.go b/go-controller/pkg/clustermanager/network_cluster_controller.go index 3dfbba9f40..42e37b8aa1 100644 --- a/go-controller/pkg/clustermanager/network_cluster_controller.go +++ b/go-controller/pkg/clustermanager/network_cluster_controller.go @@ -187,25 +187,27 @@ func (ncc *networkClusterController) init() error { if util.IsNetworkSegmentationSupportEnabled() && ncc.IsPrimaryNetwork() { // if the network is a primary L2 UDN network, then we need to reserve // the IDs used by each node in this network's pod allocator - nodes, err := ncc.watchFactory.GetNodes() + udnNodes, err := ncc.watchFactory.GetUDNNodes(ncc.NetInfo.GetNetworkName()) if err != nil { - return fmt.Errorf("failed to list node objects: %w", err) + return fmt.Errorf("failed to list udnNode objects: %w", err) } - for _, node := range nodes { - tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, ncc.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - klog.Warningf("tunnelID annotation does not exist for the node %s for network %s, err: %v; we need to allocate it...", - node.Name, ncc.GetNetworkName(), err) - } else { - return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %v", - node.Name, ncc.GetNetworkName(), err) - } + for _, udnNode := range udnNodes { + nodeName := udnNode.Labels["nodeName"] + if len(nodeName) == 0 { + klog.Errorf("UDN Node is somehow missing nodeName label!: %#v", udnNode) + continue } - if tunnelID != util.InvalidID { - if err := ncc.tunnelIDAllocator.ReserveID(ncc.GetNetworkName()+"_"+node.Name, tunnelID); err != nil { - return fmt.Errorf("unable to reserve id for network %s, node %s: %w", ncc.GetNetworkName(), node.Name, err) - } + if udnNode.Spec.Layer2TunnelID == nil { + klog.Warningf("tunnelID does not exist for UDN Node: %s, node %s for network %s, err: %v; we need to allocate it...", + udnNode.Name, nodeName, ncc.GetNetworkName(), err) + continue + } + if *udnNode.Spec.Layer2TunnelID <= util.NoID { + klog.Errorf("UDN Node: %q, node: %q has invalid tunnel id: %d", udnNode.Name, nodeName, *udnNode.Spec.Layer2TunnelID) + continue + } + if err := ncc.tunnelIDAllocator.ReserveID(ncc.GetNetworkName()+"_"+nodeName, *udnNode.Spec.Layer2TunnelID); err != nil { + return fmt.Errorf("unable to reserve id for network %s, node %s: %w", ncc.GetNetworkName(), nodeName, err) } } } @@ -214,7 +216,7 @@ func (ncc *networkClusterController) init() error { if ncc.hasNodeAllocation() { ncc.retryNodes = ncc.newRetryFramework(factory.NodeType, true) ncc.nodeAllocator = node.NewNodeAllocator(networkID, ncc.NetInfo, ncc.watchFactory.NodeCoreInformer().Lister(), - ncc.watchFactory.UserDefinedNodeInformer().Lister(), ncc.kube, ncc.tunnelIDAllocator) + ncc.watchFactory.UserDefinedNodeInformer().Lister(), ncc.kube, ncc.tunnelIDAllocator, ncc.watchFactory) err := ncc.nodeAllocator.Init() if err != nil { return fmt.Errorf("failed to initialize host subnet ip allocator: %w", err) diff --git a/go-controller/pkg/clustermanager/node/node_allocator.go b/go-controller/pkg/clustermanager/node/node_allocator.go index 6914c0301a..a2800f6b5f 100644 --- a/go-controller/pkg/clustermanager/node/node_allocator.go +++ b/go-controller/pkg/clustermanager/node/node_allocator.go @@ -4,12 +4,13 @@ import ( "fmt" udnnodev1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" v1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/listers/udnnode/v1" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" + utilerrors "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/errors" kerrors "k8s.io/apimachinery/pkg/api/errors" "net" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" listers "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" @@ -39,6 +40,7 @@ type NodeAllocator struct { kube kube.InterfaceOVN nodeLister listers.NodeLister udnNodeLister v1.UDNNodeLister + watchFactory *factory.WatchFactory // idAllocator of IDs within the network idAllocator id.Allocator clusterSubnetAllocator SubnetAllocator @@ -54,7 +56,7 @@ type NodeAllocator struct { } func NewNodeAllocator(networkID int, netInfo util.NetInfo, nodeLister listers.NodeLister, udnNodeLister v1.UDNNodeLister, - kube kube.InterfaceOVN, tunnelIDAllocator id.Allocator) *NodeAllocator { + kube kube.InterfaceOVN, tunnelIDAllocator id.Allocator, watchFactory *factory.WatchFactory) *NodeAllocator { na := &NodeAllocator{ kube: kube, nodeLister: nodeLister, @@ -64,6 +66,7 @@ func NewNodeAllocator(networkID int, netInfo util.NetInfo, nodeLister listers.No clusterSubnetAllocator: NewSubnetAllocator(), hybridOverlaySubnetAllocator: NewSubnetAllocator(), idAllocator: tunnelIDAllocator, + watchFactory: watchFactory, } if na.hasNodeSubnetAllocation() { @@ -207,6 +210,7 @@ func (na *NodeAllocator) HandleAddUpdateNodeEvent(node *corev1.Node) error { } func (na *NodeAllocator) syncUDNNodeNetworkAnnotations(node *corev1.Node) error { + // TODO(trozet): change this naming scheme, maybe hash since max node name length would break adding suffix udnNodeName := fmt.Sprintf("%s-%d", node.Name, na.networkID) udnNode, err := na.udnNodeLister.Get(udnNodeName) if err != nil && !kerrors.IsNotFound(err) { @@ -270,6 +274,17 @@ func (na *NodeAllocator) syncUDNNodeNetworkAnnotations(node *corev1.Node) error spec.NetworkID = &na.networkID } + newTunnelID := util.NoID + if util.IsNetworkSegmentationSupportEnabled() && na.netInfo.IsPrimaryNetwork() && util.DoesNetworkRequireTunnelIDs(na.netInfo) { + if newTunnelID, err = na.idAllocator.AllocateID(na.netInfo.GetNetworkName() + "_" + node.Name); err != nil { + return fmt.Errorf("failed to assign node %s tunnel id for network %s: %w", node.Name, na.netInfo.GetNetworkName(), err) + } + spec.Layer2TunnelID = &newTunnelID + + } + + // note should defer de-allocation if resource creation fails + x := &udnnodev1.UDNNode{ ObjectMeta: metav1.ObjectMeta{ Name: udnNodeName, @@ -282,10 +297,13 @@ func (na *NodeAllocator) syncUDNNodeNetworkAnnotations(node *corev1.Node) error } y, err := na.kube.CreateUDNNode(x) + if err != nil { + return err + } if y != nil { klog.Infof("UDNNode created: %#v", *y) } - return err + return nil } func (na *NodeAllocator) syncDefaultNodeNetworkAnnotations(node *corev1.Node) error { @@ -425,8 +443,8 @@ func (na *NodeAllocator) HandleDeleteNode(node *corev1.Node) error { na.clusterSubnetAllocator.ReleaseAllNetworks(node.Name) na.recordSubnetUsage() } - - return nil + err := na.kube.DeleteUDNNode(util.GetUDNNodeFormat(node.Name, na.netInfo.GetNetworkName())) + return err } func (na *NodeAllocator) Sync(nodes []interface{}) error { @@ -527,29 +545,25 @@ func (na *NodeAllocator) updateNodeNetworkAnnotationsWithRetry(nodeName string, func (na *NodeAllocator) Cleanup() error { networkName := na.netInfo.GetNetworkName() - // remove hostsubnet annotation for this network - klog.Infof("Remove node-subnets annotation for network %s on all nodes", networkName) - existingNodes, err := na.nodeLister.List(labels.Everything()) + existingUDNNodes, err := na.watchFactory.GetUDNNodes(networkName) if err != nil { - return fmt.Errorf("error in retrieving the nodes: %v", err) + return fmt.Errorf("error in retrieving the UDN nodes: %v", err) } - - for _, node := range existingNodes { - if util.NoHostSubnet(node) { - // Secondary network subnet is not allocated for a nohost subnet node - klog.V(5).Infof("Node %s is not managed by OVN", node.Name) - continue + klog.Infof("Remove UDN Nodes CRD for network %s on all nodes", networkName) + var aggregatedErrors []error + for _, udnNode := range existingUDNNodes { + if err = na.kube.DeleteUDNNode(udnNode.Name); err != nil { + aggregatedErrors = append(aggregatedErrors, err) } - - hostSubnetsMap := map[string][]*net.IPNet{networkName: nil} - // passing util.InvalidID deletes the network/tunnel id annotation for the network. - err = na.updateNodeNetworkAnnotationsWithRetry(node.Name, hostSubnetsMap, util.InvalidID, util.InvalidID, nil) - if err != nil { - return fmt.Errorf("failed to clear node %q subnet annotation for network %s", - node.Name, networkName) + if len(udnNode.Spec.NodeSubnets) > 0 { + if nodeName, ok := udnNode.Labels["nodeName"]; ok { + na.clusterSubnetAllocator.ReleaseAllNetworks(nodeName) + } } + } - na.clusterSubnetAllocator.ReleaseAllNetworks(node.Name) + if len(aggregatedErrors) > 0 { + return utilerrors.Join(aggregatedErrors...) } return nil diff --git a/go-controller/pkg/crd/udnnode/v1/types.go b/go-controller/pkg/crd/udnnode/v1/types.go index 77e2952230..022b5eb253 100644 --- a/go-controller/pkg/crd/udnnode/v1/types.go +++ b/go-controller/pkg/crd/udnnode/v1/types.go @@ -63,7 +63,7 @@ type UDNNodeSpec struct { ManagementPortMACAddress string `json:"managementPortMACAddress,omitempty"` - Layer2TunnelID int `json:"layer2TunnelID,omitempty"` + Layer2TunnelID *int `json:"layer2TunnelID,omitempty"` } // UDNNodeStatus defines the observed state of UDNNode diff --git a/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go index f7076ece3c..404f038836 100644 --- a/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go +++ b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go @@ -124,6 +124,11 @@ func (in *UDNNodeSpec) DeepCopyInto(out *UDNNodeSpec) { *out = make(DualStackCIDRs, len(*in)) copy(*out, *in) } + if in.Layer2TunnelID != nil { + in, out := &in.Layer2TunnelID, &out.Layer2TunnelID + *out = new(int) + **out = **in + } return } diff --git a/go-controller/pkg/kube/kube.go b/go-controller/pkg/kube/kube.go index 37ba62301d..922c768460 100644 --- a/go-controller/pkg/kube/kube.go +++ b/go-controller/pkg/kube/kube.go @@ -46,6 +46,7 @@ type InterfaceOVN interface { UpdateIPAMClaimIPs(updatedIPAMClaim *ipamclaimsapi.IPAMClaim) error CreateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) UpdateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) + DeleteUDNNode(udnNodeName string) error } // Interface represents the exported methods for dealing with getting/setting @@ -476,3 +477,7 @@ func (k *KubeOVN) CreateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, func (k *KubeOVN) UpdateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) { return k.UDNNodeClient.K8sV1().UDNNodes().Update(context.TODO(), udnNode, metav1.UpdateOptions{}) } + +func (k *KubeOVN) DeleteUDNNode(udnNodeName string) error { + return k.UDNNodeClient.K8sV1().UDNNodes().Delete(context.TODO(), udnNodeName, metav1.DeleteOptions{}) +} diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index dd5923d29d..82176f7ec0 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -100,6 +100,9 @@ func (h *secondaryLayer2NetworkControllerEventHandler) IsResourceScheduled(obj i // Given an object to add and a boolean specifying if the function was executed from iterateRetryResources func (h *secondaryLayer2NetworkControllerEventHandler) AddResource(obj interface{}, fromRetryLoop bool) error { switch h.objType { + case factory.NodeType: + // do nothing + return nil case factory.UserDefinedNodeType: udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { @@ -551,6 +554,10 @@ func (oc *SecondaryLayer2NetworkController) initRetryFramework() { oc.retryNamespaces = oc.newRetryFramework(factory.NamespaceType) oc.retryMultiNetworkPolicies = oc.newRetryFramework(factory.MultiNetworkPolicyType) } + + if config.OVNKubernetesFeature.EnableNetworkSegmentation { + oc.retryUDNNodes = oc.newRetryFramework(factory.UserDefinedNodeType) + } } // newRetryFramework builds and returns a retry framework for the input resource type; @@ -726,10 +733,10 @@ func (oc *SecondaryLayer2NetworkController) addPortForRemoteNodeGR(nodeJoinSubne types.TopologyExternalID: oc.TopologyType(), types.NodeExternalID: nodeName, } - tunnelID := udnNode.Spec.Layer2TunnelID - if tunnelID == util.NoID { + if udnNode.Spec.Layer2TunnelID == nil { return types.NewSuppressedError(fmt.Errorf("tunnel ID not set for node %s, UDN Node: %s", nodeName, udnNode.Name)) } + tunnelID := *udnNode.Spec.Layer2TunnelID logicalSwitchPort.Options = map[string]string{ "requested-tnl-key": strconv.Itoa(tunnelID), "requested-chassis": nodeName, diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index d104444495..924f7454ec 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -708,11 +708,18 @@ func (zic *ZoneInterconnectHandler) getStaticRoutes(ipPrefixes []*net.IPNet, nex } func (zic *ZoneInterconnectHandler) getNetworkId() (int, error) { - nodes, err := zic.watchFactory.GetNodes() - if err != nil { + if zic.IsDefault() { + nodes, err := zic.watchFactory.GetNodes() + if err != nil { + return util.InvalidID, err + } + return zic.getNetworkIdFromNodes(nodes) + } + networkID, err := zic.getNetworkIDFromUDNNodes() + if err != nil || networkID <= util.NoID { return util.InvalidID, err } - return zic.getNetworkIdFromNodes(nodes) + return networkID, nil } // getNetworkId returns the cached network ID or looks it up in any of the provided nodes From 4cc6bcab481f6b033147334f96d16b975f0e402b Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Thu, 21 Nov 2024 08:57:00 -0500 Subject: [PATCH 10/17] Speed up join subnet changed check Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/ovn.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index 7a89aa9bbf..47d81b92e3 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -440,9 +440,7 @@ func udnNodeSubnetChanged(oldNode, node *userdefinednodeapi.UDNNode) bool { } func joinCIDRChanged(oldNode, node *kapi.Node, netName string) bool { - oldSubnets, _ := util.ParseNodeGatewayRouterJoinNetwork(oldNode, netName) - newSubnets, _ := util.ParseNodeGatewayRouterJoinNetwork(node, netName) - return !reflect.DeepEqual(oldSubnets, newSubnets) + return !reflect.DeepEqual(oldNode.Annotations[util.OVNNodeGRLRPAddrs], node.Annotations[util.OVNNodeGRLRPAddrs]) } func primaryAddrChanged(oldNode, newNode *kapi.Node) bool { From 9c141b6eb7e45136345dc11ab9a3aeed3915ebfb Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Thu, 21 Nov 2024 12:25:49 -0500 Subject: [PATCH 11/17] Fixes layer2. L2 now works Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/gateway.go | 38 ++++++++++++------- .../secondary_layer2_network_controller.go | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 651e3d4140..95f19fe92d 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -357,21 +357,33 @@ func (gw *GatewayManager) GatewayInit( types.TopologyExternalID: gw.netInfo.TopologyType(), } if util.IsNetworkSegmentationSupportEnabled() && gw.netInfo.IsPrimaryNetwork() && gw.netInfo.TopologyType() == types.Layer2Topology { - node, err := gw.watchFactory.GetNode(nodeName) - if err != nil { - return fmt.Errorf("failed to fetch node %s from watch factory %w", node, err) - } - tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, gw.netInfo.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // remote node may not have the annotation yet, suppress it - return types.NewSuppressedError(err) + if gw.netInfo.IsDefault() { + node, err := gw.watchFactory.GetNode(nodeName) + if err != nil { + return fmt.Errorf("failed to fetch node %s from watch factory %w", node, err) + } + tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, gw.netInfo.GetNetworkName()) + if err != nil { + if util.IsAnnotationNotSetError(err) { + // remote node may not have the annotation yet, suppress it + return types.NewSuppressedError(err) + } + // Don't consider this node as cluster-manager has not allocated node id yet. + return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %w", + nodeName, gw.netInfo.GetNetworkName(), err) + } + logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(tunnelID) + } else { + udnNode, err := gw.watchFactory.GetUDNNodeByLabels(nodeName, gw.netInfo.GetNetworkName()) + if err != nil { + return fmt.Errorf("failed to fetch udn node %s/%s from watch factory %w", nodeName, gw.netInfo.GetNetworkName(), err) + } + if udnNode.Spec.Layer2TunnelID == nil { + return fmt.Errorf("layer 2 tunnel id is not populated in UDN Node: %s for node/network %s/%s", + udnNode.Name, nodeName, gw.netInfo.GetNetworkName()) } - // Don't consider this node as cluster-manager has not allocated node id yet. - return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %w", - nodeName, gw.netInfo.GetNetworkName(), err) + logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(*udnNode.Spec.Layer2TunnelID) } - logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(tunnelID) } } sw := nbdb.LogicalSwitch{Name: gw.joinSwitchName} diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 82176f7ec0..3adbdf9fef 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -608,7 +608,7 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(udnNode *use oc.gatewaysFailed.Store(node.Name, true) } - if err != nil && len(gwLRPIPs) > 0 { + if err == nil && len(gwLRPIPs) > 0 { if err := gwManager.syncNodeGateway( node, gwConfig.config, From c42d3c071f491bbf5d569afc3bcf64e6bbd777f2 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Thu, 21 Nov 2024 15:47:23 -0500 Subject: [PATCH 12/17] Optimizes nodetracker some We still need workqueue and retry for nodetracker, as well as to abstract to a singleton. The start up time of adding all nodes is a waste everytime we create a new UDN. Signed-off-by: Tim Rozet --- .../ovn/controller/services/node_tracker.go | 127 +++++++++++++++--- .../services/services_controller.go | 28 ++-- .../pkg/ovn/default_network_controller.go | 2 + .../secondary_layer2_network_controller.go | 2 + .../secondary_layer3_network_controller.go | 2 + go-controller/pkg/util/subnet_annotations.go | 2 +- 6 files changed, 132 insertions(+), 31 deletions(-) diff --git a/go-controller/pkg/ovn/controller/services/node_tracker.go b/go-controller/pkg/ovn/controller/services/node_tracker.go index 6a4df574c4..85b7aefe68 100644 --- a/go-controller/pkg/ovn/controller/services/node_tracker.go +++ b/go-controller/pkg/ovn/controller/services/node_tracker.go @@ -7,6 +7,8 @@ import ( "sync" globalconfig "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + userdefinednodeapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1" + userdefinednodeinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" @@ -34,6 +36,8 @@ type nodeTracker struct { zone string netInfo util.NetInfo + + watchFactory *factory.WatchFactory } type nodeInfo struct { @@ -78,18 +82,19 @@ func (ni *nodeInfo) l3gatewayAddressesStr() []string { return out } -func newNodeTracker(zone string, resyncFn func(nodes []nodeInfo), netInfo util.NetInfo) *nodeTracker { +func newNodeTracker(zone string, resyncFn func(nodes []nodeInfo), netInfo util.NetInfo, watchFactory *factory.WatchFactory) *nodeTracker { return &nodeTracker{ - nodes: map[string]nodeInfo{}, - zone: zone, - resyncFn: resyncFn, - netInfo: netInfo, + nodes: map[string]nodeInfo{}, + zone: zone, + resyncFn: resyncFn, + netInfo: netInfo, + watchFactory: watchFactory, } } -func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer) (cache.ResourceEventHandlerRegistration, error) { - return nodeInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace(cache.ResourceEventHandlerFuncs{ +func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer, udnNodeInformer userdefinednodeinformer.UDNNodeInformer) (cache.ResourceEventHandlerRegistration, cache.ResourceEventHandlerRegistration, error) { + nodeHandler, err := nodeInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { node, ok := obj.(*v1.Node) if !ok { @@ -119,8 +124,7 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer) (cache.Res // - node changes its zone // - node becomes a hybrid overlay node from a ovn node or vice verse // . No need to trigger update for any other field change. - if util.NodeSubnetAnnotationChanged(oldObj, newObj) || - util.NodeL3GatewayAnnotationChanged(oldObj, newObj) || + if util.NodeL3GatewayAnnotationChanged(oldObj, newObj) || oldObj.Name != newObj.Name || util.NodeHostCIDRsAnnotationChanged(oldObj, newObj) || util.NodeZoneAnnotationChanged(oldObj, newObj) || @@ -128,6 +132,10 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer) (cache.Res util.NoHostSubnet(oldObj) != util.NoHostSubnet(newObj) { nt.updateNode(newObj) } + + if nt.netInfo.IsDefault() && util.NodeSubnetAnnotationChanged(oldObj, newObj) { + nt.updateNode(newObj) + } }, DeleteFunc: func(obj interface{}) { node, ok := obj.(*v1.Node) @@ -147,6 +155,52 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer) (cache.Res }, })) + if err != nil { + return nil, nil, err + } + + // node tracker has no workqueue and retry logic wtf... + // TODO(trozet): refactor node tracker with a workqueue + udnNodeHandler, err := udnNodeInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + // need to handle add because node tracker update fired on node may not be able to get the pod subnet + // which comes on the UDN CRD + udnNode, ok := obj.(*userdefinednodeapi.UDNNode) + if !ok { + klog.Errorf("could not cast %T object to *userdefinednodeapi.UDNNode", obj) + return + } + + if nt.netInfo.GetNetworkName() != udnNode.GetLabels()["networkName"] { + return + } + nodeName := udnNode.GetLabels()["nodeName"] + if nodeName == "" { + klog.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) + return + } + node, err := nt.watchFactory.GetNode(nodeName) + if err != nil { + klog.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, udnNode.Name) + return + } + // hopefully node tracker is thread safe... + nt.updateNode(node) + }, + UpdateFunc: func(old, new interface{}) { + // only thing here would be to check if subnets changed which isn't possible for UDN + return + }, + DeleteFunc: func(obj interface{}) { + // no need to delete here, node delete will take care of it + return + }, + })) + if err != nil { + return nil, nil, err + } + return nodeHandler, udnNodeHandler, nil + } // updateNodeInfo updates the node info cache, and syncs all services @@ -211,12 +265,20 @@ func (nt *nodeTracker) updateNode(node *v1.Node) { klog.V(2).Infof("Processing possible switch / router updates for node %s", node.Name) var hsn []*net.IPNet var err error + var udnNode *userdefinednodeapi.UDNNode if nt.netInfo.TopologyType() == types.Layer2Topology { for _, subnet := range nt.netInfo.Subnets() { hsn = append(hsn, subnet.CIDR) } } else { - hsn, err = util.ParseNodeHostSubnetAnnotation(node, nt.netInfo.GetNetworkName()) + if nt.netInfo.IsDefault() { + hsn, err = util.ParseNodeHostSubnetAnnotation(node, nt.netInfo.GetNetworkName()) + } else { + udnNode, err = nt.watchFactory.GetUDNNodeByLabels(node.Name, nt.netInfo.GetNetworkName()) + if err != nil { + hsn, err = util.ParseNodeUDNHostSubnet(udnNode) + } + } } if err != nil || hsn == nil || util.NoHostSubnet(node) { // usually normal; means the node's gateway hasn't been initialized yet @@ -233,18 +295,43 @@ func (nt *nodeTracker) updateNode(node *v1.Node) { // if the node has a gateway config, it will soon have a gateway router // so, set the router name - gwConf, err := util.ParseNodeL3GatewayAnnotation(node) - if err != nil || gwConf == nil { - klog.Infof("Node %s has invalid / no gateway config: %v", node.Name, err) - } else if gwConf.Mode != globalconfig.GatewayModeDisabled { - grName = nt.netInfo.GetNetworkScopedGWRouterName(node.Name) - // L3 GW IP addresses are not network-specific, we can take them from the default L3 GW annotation - for _, ip := range gwConf.IPAddresses { - l3gatewayAddresses = append(l3gatewayAddresses, ip.IP) + if nt.netInfo.IsDefault() { + var gwConf *util.L3GatewayConfig + gwConf, err = util.ParseNodeL3GatewayAnnotation(node) + if err != nil || gwConf == nil { + klog.Infof("Node %s has invalid / no gateway config: %v", node.Name, err) + } else if gwConf.Mode != globalconfig.GatewayModeDisabled { + for _, ip := range gwConf.IPAddresses { + l3gatewayAddresses = append(l3gatewayAddresses, ip.IP) + } + nodePortEnabled = gwConf.NodePortEnable + } + } else { + if udnNode == nil { + udnNode, err = nt.watchFactory.GetUDNNodeByLabels(node.Name, nt.netInfo.GetNetworkName()) + if err != nil { + klog.Infof("Node %s has invalid / no udn config: %v", node.Name, err) + } + } + if udnNode != nil { + joinCIDRs, err := util.ParseNodeUDNGatewayRouterJoinAddrs(udnNode) + // ignore gateway disabled mode, from l3gw config. Assume if join addrs exist then it must not be disabled + if err != nil { + klog.Infof("UDN Node %s has invalid / no gateway config: %v", node.Name, err) + } else { + for _, cidr := range joinCIDRs { + l3gatewayAddresses = append(l3gatewayAddresses, cidr.IP) + } + } + // assume node port enabled for UDN, may need to put this in UDN Node spec + nodePortEnabled = true } - nodePortEnabled = gwConf.NodePortEnable - chassisID = gwConf.ChassisID } + if len(l3gatewayAddresses) > 0 { + grName = nt.netInfo.GetNetworkScopedGWRouterName(node.Name) + chassisID = node.Annotations[util.OvnNodeChassisID] + } + hostAddresses, err := util.GetNodeHostAddrs(node) if err != nil { klog.Warningf("Failed to get node host CIDRs for [%s]: %s", node.Name, err.Error()) diff --git a/go-controller/pkg/ovn/controller/services/services_controller.go b/go-controller/pkg/ovn/controller/services/services_controller.go index 6ce13ffcc8..bc06bee0f9 100644 --- a/go-controller/pkg/ovn/controller/services/services_controller.go +++ b/go-controller/pkg/ovn/controller/services/services_controller.go @@ -12,6 +12,7 @@ import ( "golang.org/x/time/rate" globalconfig "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + userdefinednodeinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/udnnode/v1/apis/informers/externalversions/udnnode/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" @@ -63,9 +64,11 @@ func NewController(client clientset.Interface, serviceInformer coreinformers.ServiceInformer, endpointSliceInformer discoveryinformers.EndpointSliceInformer, nodeInformer coreinformers.NodeInformer, + udnNodeInformer userdefinednodeinformer.UDNNodeInformer, nadController networkAttachDefController.NADController, recorder record.EventRecorder, netInfo util.NetInfo, + watchFactory *factory.WatchFactory, ) (*Controller, error) { klog.V(4).Infof("Creating services controller for network=%s", netInfo.GetNetworkName()) c := &Controller{ @@ -85,11 +88,14 @@ func NewController(client clientset.Interface, endpointSliceLister: endpointSliceInformer.Lister(), nadController: nadController, - eventRecorder: recorder, - repair: newRepair(serviceInformer.Lister(), nbClient), - nodeInformer: nodeInformer, - nodesSynced: nodeInformer.Informer().HasSynced, - netInfo: netInfo, + eventRecorder: recorder, + repair: newRepair(serviceInformer.Lister(), nbClient), + nodeInformer: nodeInformer, + nodesSynced: nodeInformer.Informer().HasSynced, + udnNodeInformer: udnNodeInformer, + udnNodesSynced: udnNodeInformer.Informer().HasSynced, + + netInfo: netInfo, } zone, err := libovsdbutil.GetNBZone(c.nbClient) if err != nil { @@ -98,7 +104,7 @@ func NewController(client clientset.Interface, // load balancers need to be applied to nodes, so // we need to watch Node objects for changes. // Need to re-sync all services when a node gains its switch or GWR - c.nodeTracker = newNodeTracker(zone, c.RequestFullSync, netInfo) + c.nodeTracker = newNodeTracker(zone, c.RequestFullSync, netInfo, watchFactory) return c, nil } @@ -119,7 +125,8 @@ type Controller struct { nadController networkAttachDefController.NADController - nodesSynced cache.InformerSynced + nodesSynced cache.InformerSynced + udnNodesSynced cache.InformerSynced // Services that need to be updated. A channel is inappropriate here, // because it allows services with lots of pods to be serviced much @@ -134,7 +141,8 @@ type Controller struct { // repair contains a controller that keeps in sync OVN and Kubernetes services repair *repair - nodeInformer coreinformers.NodeInformer + nodeInformer coreinformers.NodeInformer + udnNodeInformer userdefinednodeinformer.UDNNodeInformer // nodeTracker nodeTracker *nodeTracker @@ -181,7 +189,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, runRepair, useLBGr klog.Infof("Starting controller %s for network=%s", controllerName, c.netInfo.GetNetworkName()) defer klog.Infof("Shutting down controller %s for network=%s", controllerName, c.netInfo.GetNetworkName()) - nodeHandler, err := c.nodeTracker.Start(c.nodeInformer) + nodeHandler, udnNodeHandler, err := c.nodeTracker.Start(c.nodeInformer, c.udnNodeInformer) if err != nil { return err } @@ -190,7 +198,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, runRepair, useLBGr c.startupDoneLock.Lock() c.startupDone = false c.startupDoneLock.Unlock() - if !util.WaitForHandlerSyncWithTimeout(nodeControllerName, stopCh, types.HandlerSyncTimeout, nodeHandler.HasSynced) { + if !util.WaitForHandlerSyncWithTimeout(nodeControllerName, stopCh, types.HandlerSyncTimeout, nodeHandler.HasSynced, udnNodeHandler.HasSynced) { return fmt.Errorf("error syncing node tracker handler") } diff --git a/go-controller/pkg/ovn/default_network_controller.go b/go-controller/pkg/ovn/default_network_controller.go index c0f7c0c67b..5900f66db2 100644 --- a/go-controller/pkg/ovn/default_network_controller.go +++ b/go-controller/pkg/ovn/default_network_controller.go @@ -164,9 +164,11 @@ func newDefaultNetworkControllerCommon(cnci *CommonNetworkControllerInfo, cnci.watchFactory.ServiceCoreInformer(), cnci.watchFactory.EndpointSliceCoreInformer(), cnci.watchFactory.NodeCoreInformer(), + cnci.watchFactory.UserDefinedNodeInformer(), nadController, cnci.recorder, &util.DefaultNetInfo{}, + cnci.watchFactory, ) if err != nil { return nil, fmt.Errorf("unable to create new service controller while creating new default network controller: %w", err) diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 3adbdf9fef..e107860de2 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -367,9 +367,11 @@ func NewSecondaryLayer2NetworkController(cnci *CommonNetworkControllerInfo, netI cnci.watchFactory.ServiceCoreInformer(), cnci.watchFactory.EndpointSliceCoreInformer(), cnci.watchFactory.NodeCoreInformer(), + cnci.watchFactory.UserDefinedNodeInformer(), nadController, cnci.recorder, netInfo, + cnci.watchFactory, ) if err != nil { return nil, fmt.Errorf("unable to create new service controller while creating new layer2 network controller: %w", err) diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 3378eb43ca..05fadd2bfe 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -426,9 +426,11 @@ func NewSecondaryLayer3NetworkController(cnci *CommonNetworkControllerInfo, netI cnci.watchFactory.ServiceCoreInformer(), cnci.watchFactory.EndpointSliceCoreInformer(), cnci.watchFactory.NodeCoreInformer(), + cnci.watchFactory.UserDefinedNodeInformer(), nadController, cnci.recorder, netInfo, + cnci.watchFactory, ) if err != nil { return nil, fmt.Errorf("unable to create new service controller for network=%s: %w", netInfo.GetNetworkName(), err) diff --git a/go-controller/pkg/util/subnet_annotations.go b/go-controller/pkg/util/subnet_annotations.go index 0740b1dea7..a95b11f953 100644 --- a/go-controller/pkg/util/subnet_annotations.go +++ b/go-controller/pkg/util/subnet_annotations.go @@ -167,7 +167,7 @@ func DeleteNodeHostSubnetAnnotation(nodeAnnotator kube.Annotator) { } func ParseNodeUDNHostSubnet(udnNode *userdefinednodeapi.UDNNode) ([]*net.IPNet, error) { - nets := []*net.IPNet{} + var nets []*net.IPNet for _, subnet := range udnNode.Spec.NodeSubnets { _, ipnet, err := net.ParseCIDR(string(subnet)) if err != nil { From 19271ac34a8b2f8c6f237e8931a5214bb55fc1e9 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 22 Nov 2024 14:31:45 -0500 Subject: [PATCH 13/17] More fixes...things now all seem to work Signed-off-by: Tim Rozet --- .../pkg/ovn/controller/services/node_tracker.go | 3 ++- .../ovn/controller/services/services_controller.go | 2 +- go-controller/pkg/ovn/master.go | 11 ++++++++++- .../pkg/ovn/zone_interconnect/chassis_handler.go | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/go-controller/pkg/ovn/controller/services/node_tracker.go b/go-controller/pkg/ovn/controller/services/node_tracker.go index 85b7aefe68..c07fb0432b 100644 --- a/go-controller/pkg/ovn/controller/services/node_tracker.go +++ b/go-controller/pkg/ovn/controller/services/node_tracker.go @@ -199,6 +199,7 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer, udnNodeInf if err != nil { return nil, nil, err } + return nodeHandler, udnNodeHandler, nil } @@ -275,7 +276,7 @@ func (nt *nodeTracker) updateNode(node *v1.Node) { hsn, err = util.ParseNodeHostSubnetAnnotation(node, nt.netInfo.GetNetworkName()) } else { udnNode, err = nt.watchFactory.GetUDNNodeByLabels(node.Name, nt.netInfo.GetNetworkName()) - if err != nil { + if err == nil { hsn, err = util.ParseNodeUDNHostSubnet(udnNode) } } diff --git a/go-controller/pkg/ovn/controller/services/services_controller.go b/go-controller/pkg/ovn/controller/services/services_controller.go index bc06bee0f9..0451e5b005 100644 --- a/go-controller/pkg/ovn/controller/services/services_controller.go +++ b/go-controller/pkg/ovn/controller/services/services_controller.go @@ -191,7 +191,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, runRepair, useLBGr nodeHandler, udnNodeHandler, err := c.nodeTracker.Start(c.nodeInformer, c.udnNodeInformer) if err != nil { - return err + return fmt.Errorf("failed to add node and udnNode handlers with node tracker start: %w", err) } // We need the node tracker to be synced first, as we rely on it to properly reprogram initial per node load balancers klog.Infof("Waiting for node tracker handler to sync for network=%s", c.netInfo.GetNetworkName()) diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index 40026326c0..9b2851084c 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -630,8 +630,13 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *kapi.Node, nSy var err1 error gwLRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) if err1 != nil { + oc.gatewaysFailed.Store(node.Name, true) + oc.syncZoneICFailed.Store(node.Name, true) return fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) } + } else { + oc.gatewaysFailed.Store(node.Name, true) + oc.syncZoneICFailed.Store(node.Name, true) } } } @@ -673,7 +678,8 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *kapi.Node, nSy errs = append(errs, err) oc.syncZoneICFailed.Store(node.Name, true) } - } else { + } + if hostSubnets != nil { // Call zone IC handler's AddLocalZoneNode function to create // interconnect resources in the OVN Northbound db for this local zone node. if err := oc.zoneICHandler.AddLocalZoneNode(hostSubnets, gwLRPIPs, node); err != nil { @@ -732,8 +738,11 @@ func (oc *DefaultNetworkController) addUpdateRemoteNodeEvent(node *kapi.Node, sy var err1 error gwLRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) if err1 != nil { + oc.syncZoneICFailed.Store(node.Name, true) return fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) } + } else { + oc.syncZoneICFailed.Store(node.Name, true) } } diff --git a/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go index fb2b792ca4..d7d24fb395 100644 --- a/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go @@ -122,6 +122,7 @@ func (zic *ZoneChassisHandler) SyncNodes(kNodes []interface{}) error { // createOrUpdateNodeChassis creates or updates the node chassis to local or remote. func (zch *ZoneChassisHandler) createOrUpdateNodeChassis(node *corev1.Node, isRemote bool) error { + klog.Infof("adding node chassis: %s", node.Name) // Get the chassis id. chassisID, err := util.ParseNodeChassisIDAnnotation(node) if err != nil { From b0ad506dd1c689cb00acf5b12daad2d787181f37 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 22 Nov 2024 15:09:55 -0500 Subject: [PATCH 14/17] Fix gofmt issues Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/controller/services/node_tracker.go | 6 +++--- go-controller/pkg/ovn/zone_interconnect/chassis_handler.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go-controller/pkg/ovn/controller/services/node_tracker.go b/go-controller/pkg/ovn/controller/services/node_tracker.go index c07fb0432b..d29501cb78 100644 --- a/go-controller/pkg/ovn/controller/services/node_tracker.go +++ b/go-controller/pkg/ovn/controller/services/node_tracker.go @@ -167,7 +167,7 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer, udnNodeInf // which comes on the UDN CRD udnNode, ok := obj.(*userdefinednodeapi.UDNNode) if !ok { - klog.Errorf("could not cast %T object to *userdefinednodeapi.UDNNode", obj) + klog.Errorf("Could not cast %T object to *userdefinednodeapi.UDNNode", obj) return } @@ -176,12 +176,12 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer, udnNodeInf } nodeName := udnNode.GetLabels()["nodeName"] if nodeName == "" { - klog.Errorf("unable to find nodeName label for udn Node: %s", udnNode.Name) + klog.Errorf("Unable to find nodeName label for udn Node: %s", udnNode.Name) return } node, err := nt.watchFactory.GetNode(nodeName) if err != nil { - klog.Errorf("failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, udnNode.Name) + klog.Errorf("Failed to find corresponding node object with name :%q for UDN Node: %q", nodeName, udnNode.Name) return } // hopefully node tracker is thread safe... diff --git a/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go index d7d24fb395..47a0ea09a2 100644 --- a/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go @@ -122,7 +122,7 @@ func (zic *ZoneChassisHandler) SyncNodes(kNodes []interface{}) error { // createOrUpdateNodeChassis creates or updates the node chassis to local or remote. func (zch *ZoneChassisHandler) createOrUpdateNodeChassis(node *corev1.Node, isRemote bool) error { - klog.Infof("adding node chassis: %s", node.Name) + klog.Infof("Adding node chassis: %s", node.Name) // Get the chassis id. chassisID, err := util.ParseNodeChassisIDAnnotation(node) if err != nil { From 764a4229be9f30d78208cb6234e2d150d7e3883b Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 25 Nov 2024 09:50:38 -0500 Subject: [PATCH 15/17] Fix routes Signed-off-by: Tim Rozet --- go-controller/pkg/util/node_annotations.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index 54be6b7e6e..7638ba370d 100644 --- a/go-controller/pkg/util/node_annotations.go +++ b/go-controller/pkg/util/node_annotations.go @@ -906,18 +906,18 @@ func ParseNodeGatewayRouterJoinIPv4(node *kapi.Node, netName string) (net.IP, er } func ParseNodeUDNGatewayRouterJoinAddrs(udnNode *userdefinednodeapi.UDNNode) ([]*net.IPNet, error) { - var nets []*net.IPNet + var ipAddrs []*net.IPNet if len(udnNode.Spec.JoinSubnets) == 0 { return nil, types.NewSuppressedError(fmt.Errorf("join subnet missing in spec")) } for _, subnet := range udnNode.Spec.JoinSubnets { - _, ipnet, err := net.ParseCIDR(string(subnet)) + ip, ipNet, err := net.ParseCIDR(string(subnet)) if err != nil { return nil, err } - nets = append(nets, ipnet) + ipAddrs = append(ipAddrs, &net.IPNet{IP: ip, Mask: ipNet.Mask}) } - return nets, nil + return ipAddrs, nil } // ParseNodeGatewayRouterJoinAddrs returns the IPv4 and/or IPv6 addresses for the node's gateway router port From 8a8c81bd4c36f260c6195027f981b8d2bed46744 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 25 Nov 2024 13:08:01 -0500 Subject: [PATCH 16/17] Fixes issue with transit switch port not being created Signed-off-by: Tim Rozet --- .../ovn/secondary_layer3_network_controller.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 05fadd2bfe..88ccc757c9 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -894,15 +894,15 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(udnNode *use node.Name, oc.GetNetworkName(), err)) oc.syncZoneICFailed.Store(node.Name, true) } - if gwLRPIPs != nil { - // gets node ID from node annotation - // gets transit switch subnet - if err := oc.zoneICHandler.AddLocalZoneNode(hostSubnets, gwLRPIPs, node); err != nil { - errs = append(errs, err) - oc.syncZoneICFailed.Store(node.Name, true) - } else { - oc.syncZoneICFailed.Delete(node.Name) - } + } + if gwLRPIPs != nil { + // gets node ID from node annotation + // gets transit switch subnet + if err := oc.zoneICHandler.AddLocalZoneNode(hostSubnets, gwLRPIPs, node); err != nil { + errs = append(errs, err) + oc.syncZoneICFailed.Store(node.Name, true) + } else { + oc.syncZoneICFailed.Delete(node.Name) } } } From 0ccc6247e8491ada1f9db055a2de175edcfa45f8 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 25 Nov 2024 14:55:32 -0500 Subject: [PATCH 17/17] Threadiness improvements Make UDN Node informer have 15 threads Make Network Manager and NAD controller have higher thread count Signed-off-by: Tim Rozet --- go-controller/pkg/factory/factory.go | 9 ++++++--- .../network_attach_def_controller.go | 2 +- .../pkg/network-attach-def-controller/network_manager.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/go-controller/pkg/factory/factory.go b/go-controller/pkg/factory/factory.go index 1056f0f9ae..8c111f40af 100644 --- a/go-controller/pkg/factory/factory.go +++ b/go-controller/pkg/factory/factory.go @@ -420,7 +420,7 @@ func NewOVNKubeControllerWatchFactory(ovnClientset *util.OVNKubeControllerClient } wf.udnNodeFactory = userdefinednodeinformerfactory.NewSharedInformerFactory(ovnClientset.UserDefinedNodeClient, resyncInterval) - udnNodeInformer := wf.udnNodeFactory.K8s().V1().UDNNodes().Informer() + udnNodeRawInformer := wf.udnNodeFactory.K8s().V1().UDNNodes().Informer() indexers := cache.Indexers{ types.UDNIndexer: func(obj interface{}) ([]string, error) { @@ -431,14 +431,17 @@ func NewOVNKubeControllerWatchFactory(ovnClientset *util.OVNKubeControllerClient return []string{util.GetUDNNodeFormat(udnNode.Labels["nodeName"], udnNode.Labels["networkName"])}, nil }, } - err := udnNodeInformer.AddIndexers(indexers) + err := udnNodeRawInformer.AddIndexers(indexers) if err != nil { return nil, err } - wf.informers[UserDefinedNodeType], err = newInformer(UserDefinedNodeType, udnNodeInformer) + + udnNodeInformer, err := newQueuedInformer(UserDefinedNodeType, udnNodeRawInformer, + wf.stopChan, defaultNumEventQueues) if err != nil { return nil, err } + wf.informers[UserDefinedNodeType] = udnNodeInformer } if util.IsMultiNetworkPoliciesSupportEnabled() { diff --git a/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go b/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go index 3647a41434..3060a13ea7 100644 --- a/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go +++ b/go-controller/pkg/network-attach-def-controller/network_attach_def_controller.go @@ -100,7 +100,7 @@ func NewNetAttachDefinitionController( RateLimiter: workqueue.DefaultTypedControllerRateLimiter[string](), Reconcile: nadController.sync, ObjNeedsUpdate: nadNeedsUpdate, - Threadiness: 1, + Threadiness: 5, } nadInformer := wf.NADInformer() diff --git a/go-controller/pkg/network-attach-def-controller/network_manager.go b/go-controller/pkg/network-attach-def-controller/network_manager.go index 63763afd50..4eb90790d4 100644 --- a/go-controller/pkg/network-attach-def-controller/network_manager.go +++ b/go-controller/pkg/network-attach-def-controller/network_manager.go @@ -49,7 +49,7 @@ func newNetworkManager(name string, ncm NetworkControllerManager) networkManager config := &controller.ReconcilerConfig{ RateLimiter: workqueue.DefaultTypedControllerRateLimiter[string](), Reconcile: nc.sync, - Threadiness: 1, + Threadiness: 15, } nc.controller = controller.NewReconciler( nc.name,