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/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-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-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/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/clustermanager/network_cluster_controller.go b/go-controller/pkg/clustermanager/network_cluster_controller.go index 71603cb627..42e37b8aa1 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{} @@ -186,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) } } } @@ -212,8 +215,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, 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 8629098812..a2800f6b5f 100644 --- a/go-controller/pkg/clustermanager/node/node_allocator.go +++ b/go-controller/pkg/clustermanager/node/node_allocator.go @@ -2,10 +2,15 @@ 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" + "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" - "k8s.io/apimachinery/pkg/labels" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" listers "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" @@ -32,8 +37,10 @@ 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 + watchFactory *factory.WatchFactory // idAllocator of IDs within the network idAllocator id.Allocator clusterSubnetAllocator SubnetAllocator @@ -48,15 +55,18 @@ 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, watchFactory *factory.WatchFactory) *NodeAllocator { na := &NodeAllocator{ kube: kube, nodeLister: nodeLister, + udnNodeLister: udnNodeLister, networkID: networkID, netInfo: netInfo, clusterSubnetAllocator: NewSubnetAllocator(), hybridOverlaySubnetAllocator: NewSubnetAllocator(), idAllocator: tunnelIDAllocator, + watchFactory: watchFactory, } if na.hasNodeSubnetAllocation() { @@ -199,10 +209,104 @@ 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 { + // 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) { + 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 + } + + if na.networkID > util.NoID { + 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, + Labels: map[string]string{ + "networkName": na.netInfo.GetNetworkName(), + "nodeName": node.Name, + }, + }, + Spec: spec, + } + + y, err := na.kube.CreateUDNNode(x) + if err != nil { + return err + } + if y != nil { + klog.Infof("UDNNode created: %#v", *y) + } + return nil +} + +func (na *NodeAllocator) syncDefaultNodeNetworkAnnotations(node *corev1.Node) error { networkName := na.netInfo.GetNetworkName() networkID, err := util.ParseNetworkIDAnnotation(node, networkName) @@ -318,6 +422,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() { @@ -329,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 { @@ -431,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/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..f0c99f2f65 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/apis/applyconfiguration/udnnode/v1/udnnodespec.go @@ -0,0 +1,78 @@ +/* + + +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 { + NetworkID *int `json:"network-id,omitempty"` + 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{} +} + +// 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. +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/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..022b5eb253 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/types.go @@ -0,0 +1,105 @@ +/* +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 { + 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. + // 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"` + + ManagementPortMACAddress string `json:"managementPortMACAddress,omitempty"` + + Layer2TunnelID *int `json:"layer2TunnelID,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 + +// +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/crd/udnnode/v1/zz_generated.deepcopy.go b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..404f038836 --- /dev/null +++ b/go-controller/pkg/crd/udnnode/v1/zz_generated.deepcopy.go @@ -0,0 +1,166 @@ +//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.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)) + copy(*out, *in) + } + if in.JoinSubnets != nil { + in, out := &in.JoinSubnets, &out.JoinSubnets + *out = make(DualStackCIDRs, len(*in)) + copy(*out, *in) + } + if in.Layer2TunnelID != nil { + in, out := &in.Layer2TunnelID, &out.Layer2TunnelID + *out = new(int) + **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/factory/factory.go b/go-controller/pkg/factory/factory.go index c6eaaf4c9f..8c111f40af 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" @@ -70,6 +71,11 @@ 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" + 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" @@ -114,6 +120,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 +194,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{}) @@ -410,6 +418,30 @@ func NewOVNKubeControllerWatchFactory(ovnClientset *util.OVNKubeControllerClient if err != nil { return nil, err } + + wf.udnNodeFactory = userdefinednodeinformerfactory.NewSharedInformerFactory(ovnClientset.UserDefinedNodeClient, resyncInterval) + udnNodeRawInformer := 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 := udnNodeRawInformer.AddIndexers(indexers) + if err != nil { + return nil, err + } + + udnNodeInformer, err := newQueuedInformer(UserDefinedNodeType, udnNodeRawInformer, + wf.stopChan, defaultNumEventQueues) + if err != nil { + return nil, err + } + wf.informers[UserDefinedNodeType] = udnNodeInformer } if util.IsMultiNetworkPoliciesSupportEnabled() { @@ -540,6 +572,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 } @@ -581,6 +622,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 @@ -726,6 +771,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 @@ -770,6 +836,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. @@ -878,11 +947,33 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset 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 + } + // 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 @@ -957,6 +1048,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) @@ -1068,7 +1163,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) } @@ -1288,11 +1390,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) @@ -1468,6 +1580,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 } @@ -1558,6 +1705,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/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/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/kube/kube.go b/go-controller/pkg/kube/kube.go index a287f058c7..922c768460 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,9 @@ 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) + UpdateUDNNode(udnNode *udnnodev1.UDNNode) (*udnnodev1.UDNNode, error) + DeleteUDNNode(udnNodeName string) error } // Interface represents the exported methods for dealing with getting/setting @@ -84,6 +89,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 +469,15 @@ 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{}) +} + +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/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, 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..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,20 +83,36 @@ 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 } // 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 +127,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..c7025392e3 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -1,7 +1,11 @@ package node import ( + "context" "fmt" + 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 +48,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 +185,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 +213,41 @@ 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 { + udnNode, err := udng.watchFactory.GetUDNNodeByLabels(udng.node.Name, udng.GetNetworkName()) + if err != nil { + return err + } + cnode := *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 +269,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) @@ -377,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/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/node/secondary_node_network_controller.go b/go-controller/pkg/node/secondary_node_network_controller.go index f3bf50f472..db18e0d763 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) } @@ -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 a1a263eeae..1bf77b70c4 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -2,6 +2,8 @@ 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" @@ -97,6 +99,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 @@ -106,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 @@ -625,12 +631,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,11 +672,11 @@ 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} - err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(bnc.nbClient, &sw, &logicalSwitchPort) + err := libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(bnc.nbClient, &sw, &logicalSwitchPort) if err != nil { return nil, err } @@ -693,7 +694,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(nodeName), + bnc.GetNetworkScopedSwitchName(nodeName), nodeName, v4Subnet); err != nil { return nil, err } } @@ -701,6 +703,22 @@ func (bnc *BaseNetworkController) syncNodeManagementPort(node *kapi.Node, switch 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 { @@ -714,6 +732,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..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 } @@ -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,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(node, 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) - } 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/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/controller/services/node_tracker.go b/go-controller/pkg/ovn/controller/services/node_tracker.go index 6a4df574c4..d29501cb78 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,53 @@ 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 +266,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 +296,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..0451e5b005 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,16 +189,16 @@ 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 + 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()) 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/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/master.go b/go-controller/pkg/ovn/master.go index 625e12c38b..9b2851084c 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,38 @@ 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 { + 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) + } + } + } + + 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 +672,22 @@ 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) - } else { - oc.syncZoneICFailed.Delete(node.Name) + if hostSubnets == nil { + hostSubnets, err = util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) + if err != nil { + errs = append(errs, err) + oc.syncZoneICFailed.Store(node.Name, true) + } + } + 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 { + errs = append(errs, err) + oc.syncZoneICFailed.Store(node.Name, true) + } else { + oc.syncZoneICFailed.Delete(node.Name) + } } } } @@ -699,10 +725,31 @@ 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 { + 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) + } + } + // 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 +783,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..47d81b92e3 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) } } @@ -405,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. @@ -424,16 +422,25 @@ 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) - return !reflect.DeepEqual(oldSubnets, newSubnets) + return !reflect.DeepEqual(oldNode.Annotations[util.OVNNodeGRLRPAddrs], node.Annotations[util.OVNNodeGRLRPAddrs]) } func primaryAddrChanged(oldNode, newNode *kapi.Node) bool { diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 21fb77b84f..e107860de2 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" @@ -100,11 +101,29 @@ func (h *secondaryLayer2NetworkControllerEventHandler) IsResourceScheduled(obj i func (h *secondaryLayer2NetworkControllerEventHandler) AddResource(obj interface{}, fromRetryLoop bool) error { switch h.objType { case factory.NodeType: - node, ok := obj.(*corev1.Node) + // do nothing + return nil + 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) { + 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) @@ -113,9 +132,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 +145,22 @@ 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) + } + h.oc.nodeMutex.Delete(nodeName) + return h.oc.deleteNodeEvent(nodeName) + case factory.NodeType: + return nil default: return h.oc.DeleteSecondaryNetworkResourceCommon(h.objType, obj, cachedObj) } @@ -153,34 +182,88 @@ 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()) + h.oc.nodeMutex.LockKey(newNode.Name) + defer h.oc.nodeMutex.UnlockKey(newNode.Name) + 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 + } - return h.oc.addUpdateLocalNodeEvent(newNode, nodeSyncsParam) + 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) + } + 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 + _, 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) } @@ -212,6 +295,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) } @@ -234,6 +320,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 @@ -280,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) @@ -313,6 +402,7 @@ func NewSecondaryLayer2NetworkController(cnci *CommonNetworkControllerInfo, netI }, }, }, + nodeMutex: syncmap.NewSyncMapComparableKey[string, bool](), mgmtPortFailed: sync.Map{}, syncZoneICFailed: sync.Map{}, gatewayManagers: sync.Map{}, @@ -466,6 +556,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; @@ -492,19 +586,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 +626,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 +651,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 +680,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 +702,7 @@ func (oc *SecondaryLayer2NetworkController) addUpdateRemoteNodeEvent(node *corev } } + // just uses node name errs = append(errs, oc.BaseSecondaryLayer2NetworkController.addUpdateRemoteNodeEvent(node)) err := utilerrors.Join(errs...) @@ -581,17 +712,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 +724,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) + 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": 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 +771,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 +797,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 +831,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 46589144e7..88ccc757c9 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" @@ -12,6 +13,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" @@ -98,12 +100,30 @@ func (h *secondaryLayer3NetworkControllerEventHandler) IsResourceScheduled(obj i func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface{}, fromRetryLoop bool) error { switch h.objType { case factory.NodeType: - node, ok := obj.(*kapi.Node) + //do nothing + return nil + 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) + } + + 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) { + 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) @@ -127,13 +147,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 +178,125 @@ 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 + 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 + 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) + } + 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) + } + 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) 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 || nodeSubnetChange + _, failed = h.oc.mgmtPortFailed.Load(node.Name) + syncMgmtPort := failed || udnNodeMACAddressChanged(oldUDNNode, newUDNNode) || nodeSubnetChange + _, syncZoneIC := h.oc.syncZoneICFailed.Load(node.Name) + _, failed = h.oc.gatewaysFailed.Load(node.Name) + syncGw := failed || nodeSubnetChange + 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 || nodeSubnetChange + return h.oc.addUpdateRemoteNodeEvent(newUDNNode, node, syncZoneIC) } default: return h.oc.UpdateSecondaryNetworkResourceCommon(h.objType, oldObj, newObj, inRetryCache) @@ -221,13 +308,22 @@ 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) - + 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) + } + h.oc.nodeMutex.Delete(nodeName) + return h.oc.deleteNodeEvent(nodeName) + case factory.NodeType: + return nil default: return h.oc.DeleteSecondaryNetworkResourceCommon(h.objType, obj, cachedObj) } @@ -256,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) } @@ -278,6 +377,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 @@ -326,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) @@ -357,6 +459,7 @@ func NewSecondaryLayer3NetworkController(cnci *CommonNetworkControllerInfo, netI nadController: nadController, }, }, + nodeMutex: syncmap.NewSyncMapComparableKey[string, bool](), mgmtPortFailed: sync.Map{}, addNodeFailed: sync.Map{}, nodeClusterRouterPortFailed: sync.Map{}, @@ -402,6 +505,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; @@ -461,6 +566,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) } @@ -542,6 +650,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 @@ -577,6 +691,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 { @@ -630,14 +760,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 +773,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 +807,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 +879,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 +887,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 +914,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 +957,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 +981,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 } @@ -859,6 +1037,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 @@ -927,7 +1110,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 +1149,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/chassis_handler.go b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go index fb2b792ca4..47a0ea09a2 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 { 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..924f7454ec 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) + 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 + } - // if not set yet, let's retry for a bit - if util.IsAnnotationNotSetError(err) { - maxTimeout := 2 * time.Minute + 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 }) @@ -215,7 +229,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 +237,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 +246,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 +256,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 +273,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 +401,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 +448,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 +458,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 +472,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 +580,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 +592,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 +600,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 +616,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 +628,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 +641,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 +655,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 @@ -746,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 @@ -777,3 +746,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 f82dbd6ef9..d35d3cee15 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" @@ -46,6 +47,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" ) @@ -65,6 +67,7 @@ type OVNClientset struct { AdminPolicyRouteClient adminpolicybasedrouteclientset.Interface IPAMClaimsClient ipamclaimssclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } // OVNMasterClientset @@ -82,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 @@ -98,6 +101,7 @@ type OVNKubeControllerClientset struct { IPAMClaimsClient ipamclaimssclientset.Interface NetworkAttchDefClient networkattchmentdefclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } type OVNNodeClientset struct { @@ -107,6 +111,7 @@ type OVNNodeClientset struct { AdminPolicyRouteClient adminpolicybasedrouteclientset.Interface NetworkAttchDefClient networkattchmentdefclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } type OVNClusterManagerClientset struct { @@ -122,6 +127,7 @@ type OVNClusterManagerClientset struct { IPAMClaimsClient ipamclaimssclientset.Interface OCPNetworkClient ocpnetworkclientset.Interface UserDefinedNetworkClient userdefinednetworkclientset.Interface + UserDefinedNodeClient userdefinednodeclientset.Interface } const ( @@ -134,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, @@ -152,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, @@ -166,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, @@ -183,6 +196,7 @@ func (cs *OVNClientset) GetOVNKubeControllerClientset() *OVNKubeControllerClient IPAMClaimsClient: cs.IPAMClaimsClient, NetworkAttchDefClient: cs.NetworkAttchDefClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } @@ -200,6 +214,7 @@ func (cs *OVNClientset) GetClusterManagerClientset() *OVNClusterManagerClientset IPAMClaimsClient: cs.IPAMClaimsClient, OCPNetworkClient: cs.OCPNetworkClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } @@ -211,6 +226,7 @@ func (cs *OVNClientset) GetNodeClientset() *OVNNodeClientset { AdminPolicyRouteClient: cs.AdminPolicyRouteClient, NetworkAttchDefClient: cs.NetworkAttchDefClient, UserDefinedNetworkClient: cs.UserDefinedNetworkClient, + UserDefinedNodeClient: cs.UserDefinedNodeClient, } } @@ -492,6 +508,8 @@ func NewOVNClientset(conf *config.KubernetesConfig) (*OVNClientset, error) { return nil, err } + userDefinedNodeClientSet, err := userdefinednodeclientset.NewForConfig(kconfig) + return &OVNClientset{ KubeClient: kclientset, ANPClient: anpClientset, @@ -506,6 +524,7 @@ func NewOVNClientset(conf *config.KubernetesConfig) (*OVNClientset, error) { AdminPolicyRouteClient: adminPolicyBasedRouteClientset, IPAMClaimsClient: ipamClaimsClientset, UserDefinedNetworkClient: userDefinedNetworkClientSet, + UserDefinedNodeClient: userDefinedNodeClientSet, }, nil } @@ -717,6 +736,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/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) diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index 34a6fa9d6f..7638ba370d 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,21 @@ func ParseNodeGatewayRouterJoinIPv4(node *kapi.Node, netName string) (net.IP, er return ip, nil } +func ParseNodeUDNGatewayRouterJoinAddrs(udnNode *userdefinednodeapi.UDNNode) ([]*net.IPNet, error) { + 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 { + ip, ipNet, err := net.ParseCIDR(string(subnet)) + if err != nil { + return nil, err + } + ipAddrs = append(ipAddrs, &net.IPNet{IP: ip, Mask: ipNet.Mask}) + } + return ipAddrs, 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 +936,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) { @@ -1495,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) +} diff --git a/go-controller/pkg/util/subnet_annotations.go b/go-controller/pkg/util/subnet_annotations.go index 11964dfbb8..a95b11f953 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) { + var 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) {