diff --git a/assets/components/ovn/common/clusterrole-controller.yaml b/assets/components/ovn/common/clusterrole-controller.yaml new file mode 100644 index 0000000000..eaacd1f96f --- /dev/null +++ b/assets/components/ovn/common/clusterrole-controller.yaml @@ -0,0 +1,153 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: openshift-ovn-kubernetes-controller +rules: +- apiGroups: [""] + resources: + - namespaces + - nodes + - pods + verbs: + - get + - list + - patch + - watch + - update +- apiGroups: [""] + resources: + - pods + verbs: + - get + - list + - patch + - watch + - delete +- apiGroups: [""] + resources: + - configmaps + verbs: + - get + - create + - update + - patch +- apiGroups: [""] + resources: + - services + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch +- apiGroups: ["networking.k8s.io"] + resources: + - networkpolicies + verbs: + - get + - list + - watch +- apiGroups: ["", "events.k8s.io"] + resources: + - events + verbs: + - create + - patch + - update +- apiGroups: ["security.openshift.io"] + resources: + - securitycontextconstraints + verbs: + - use + resourceNames: + - privileged +- apiGroups: [""] + resources: + - "nodes/status" + verbs: + - patch + - update +- apiGroups: ["k8s.ovn.org"] + resources: + - egressfirewalls + - egressips + - egressqoses + - adminpolicybasedexternalroutes + verbs: + - get + - list + - watch + - update + - patch +- apiGroups: + - k8s.ovn.org + resources: + - adminpolicybasedexternalroutes/status + verbs: [ "update"] +- apiGroups: ["cloud.network.openshift.io"] + resources: + - cloudprivateipconfigs + verbs: + - create + - patch + - update + - delete + - get + - list + - watch +- apiGroups: ["apiextensions.k8s.io"] + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch +- apiGroups: ['authentication.k8s.io'] + resources: ['tokenreviews'] + verbs: ['create'] +- apiGroups: ['authorization.k8s.io'] + resources: ['subjectaccessreviews'] + verbs: ['create'] +- apiGroups: [k8s.ovn.org] + resources: + - userdefinednetworks + - clusteruserdefinednetworks + verbs: + - get + - list + - watch + - patch + - update +- apiGroups: + - k8s.ovn.org + resources: + - routeadvertisements + verbs: + - list + - get + - watch +- apiGroups: + - k8s.ovn.org + resources: + - routeadvertisements/status + verbs: + - patch + - update +- apiGroups: + - frrk8s.metallb.io + resources: + - frrconfigurations + verbs: + - list + - get + - watch + - create + - delete + - update + - patch diff --git a/assets/components/ovn/common/clusterrole.yaml b/assets/components/ovn/common/clusterrole.yaml index a37236ed6a..18466bad65 100644 --- a/assets/components/ovn/common/clusterrole.yaml +++ b/assets/components/ovn/common/clusterrole.yaml @@ -71,121 +71,11 @@ rules: - apiGroups: ['authorization.k8s.io'] resources: ['subjectaccessreviews'] verbs: ['create'] - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: openshift-ovn-kubernetes-controller -rules: -- apiGroups: [""] - resources: - - namespaces - - nodes - - pods - verbs: - - get - - list - - patch - - watch - - update -- apiGroups: [""] - resources: - - pods - verbs: - - get - - list - - patch - - watch - - delete -- apiGroups: [""] - resources: - - configmaps - verbs: - - get - - create - - update - - patch -- apiGroups: [""] - resources: - - services - - endpoints - verbs: - - get - - list - - watch -- apiGroups: - - discovery.k8s.io - resources: - - endpointslices - verbs: - - list - - watch -- apiGroups: ["networking.k8s.io"] - resources: - - networkpolicies - verbs: - - get - - list - - watch -- apiGroups: ["", "events.k8s.io"] - resources: - - events - verbs: - - create - - patch - - update -- apiGroups: ["security.openshift.io"] - resources: - - securitycontextconstraints - verbs: - - use - resourceNames: - - privileged -- apiGroups: [""] - resources: - - "nodes/status" - verbs: - - patch - - update -- apiGroups: ["k8s.ovn.org"] - resources: - - egressfirewalls - - egressips - - egressqoses - - adminpolicybasedexternalroutes - verbs: - - get - - list - - watch - - update - - patch - apiGroups: - k8s.ovn.org resources: - - adminpolicybasedexternalroutes/status - verbs: [ "update"] -- apiGroups: ["cloud.network.openshift.io"] - resources: - - cloudprivateipconfigs + - routeadvertisements verbs: - - create - - patch - - update - - delete - - get - list + - get - watch -- apiGroups: ["apiextensions.k8s.io"] - resources: - - customresourcedefinitions - verbs: - - get - - list - - watch -- apiGroups: ['authentication.k8s.io'] - resources: ['tokenreviews'] - verbs: ['create'] -- apiGroups: ['authorization.k8s.io'] - resources: ['subjectaccessreviews'] - verbs: ['create'] diff --git a/assets/components/ovn/common/clusterrolebinding-controller.yaml b/assets/components/ovn/common/clusterrolebinding-controller.yaml new file mode 100644 index 0000000000..47954b9e31 --- /dev/null +++ b/assets/components/ovn/common/clusterrolebinding-controller.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: openshift-ovn-kubernetes-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: openshift-ovn-kubernetes-controller +subjects: +- kind: ServiceAccount + name: ovn-kubernetes-controller + namespace: openshift-ovn-kubernetes diff --git a/assets/components/ovn/common/clusterrolebinding.yaml b/assets/components/ovn/common/clusterrolebinding.yaml index 576d363a25..d7067c0d48 100644 --- a/assets/components/ovn/common/clusterrolebinding.yaml +++ b/assets/components/ovn/common/clusterrolebinding.yaml @@ -11,17 +11,3 @@ subjects: - kind: ServiceAccount name: ovn-kubernetes-node namespace: openshift-ovn-kubernetes - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: openshift-ovn-kubernetes-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: openshift-ovn-kubernetes-controller -subjects: -- kind: ServiceAccount - name: ovn-kubernetes-controller - namespace: openshift-ovn-kubernetes diff --git a/assets/components/ovn/common/crd-cudn.yaml b/assets/components/ovn/common/crd-cudn.yaml new file mode 100644 index 0000000000..8a88fd4435 --- /dev/null +++ b/assets/components/ovn/common/crd-cudn.yaml @@ -0,0 +1,728 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: clusteruserdefinednetworks.k8s.ovn.org +spec: + group: k8s.ovn.org + names: + kind: ClusterUserDefinedNetwork + listKind: ClusterUserDefinedNetworkList + plural: clusteruserdefinednetworks + singular: clusteruserdefinednetwork + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: ClusterUserDefinedNetwork describe network request for a shared + network across namespaces. + 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: ClusterUserDefinedNetworkSpec defines the desired state of + ClusterUserDefinedNetwork. + properties: + namespaceSelector: + description: NamespaceSelector Label selector for which namespace + network should be available for. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + network: + description: Network is the user-defined-network spec + properties: + layer2: + description: Layer2 is the Layer2 topology configuration. + properties: + defaultGatewayIPs: + description: |- + defaultGatewayIPs specifies the default gateway IP used in the internal OVN topology. + + Dual-stack clusters may set 2 IPs (one for each IP family), otherwise only 1 IP 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, an IP from the subnets field is used. + items: + type: string + x-kubernetes-validations: + - message: IP is invalid + rule: isIP(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 IPs are set, they must be from different + IP families + rule: size(self) != 2 || !isIP(self[0]) || !isIP(self[1]) + || ip(self[0]).family() != ip(self[1]).family() + infrastructureSubnets: + description: |- + infrastructureSubnets specifies a list of internal CIDR ranges that OVN-Kubernetes will reserve for internal network infrastructure. + Any IP addresses within these ranges cannot be assigned to workloads. + When omitted, OVN-Kubernetes will automatically allocate IP addresses from `subnets` for its infrastructure needs. + When there are not enough available IPs in the provided infrastructureSubnets, OVN-Kubernetes will automatically allocate IP addresses from subnets for its infrastructure needs. + When `reservedSubnets` is also specified the CIDRs cannot overlap. + When `defaultGatewayIPs` is also specified, the default gateway IPs must belong to one of the infrastructure subnet CIDRs. + Each item should be in range of the specified CIDR(s) in `subnets`. + The maximum number of entries allowed is 4. + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 4 + minItems: 1 + type: array + ipam: + description: IPAM section contains IPAM-related configuration + for the network. + minProperties: 1 + properties: + lifecycle: + description: |- + Lifecycle controls IP addresses management lifecycle. + + The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an + `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested. + Only supported when mode is `Enabled`. + enum: + - Persistent + type: string + mode: + description: |- + Mode controls how much of the IP configuration will be managed by OVN. + `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs + from the selected subnet to the individual pods. + `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users + configure IP addresses for the pods. + `Disabled` is only available for Secondary networks. + By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function + (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network. + Defaults to `Enabled`. + enum: + - Enabled + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: lifecycle Persistent is only supported when ipam.mode + is Enabled + rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent'' + || !has(self.mode) || self.mode == ''Enabled''' + 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: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different + IP families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + mtu: + description: |- + MTU is the maximum transmission unit for a network. + MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. + format: int32 + maximum: 65536 + minimum: 576 + type: integer + reservedSubnets: + description: |- + reservedSubnets specifies a list of CIDRs reserved for static IP assignment, excluded from automatic allocation. + reservedSubnets is optional. When omitted, all IP addresses in `subnets` are available for automatic assignment. + IPs from these ranges can still be requested through static IP assignment. + Each item should be in range of the specified CIDR(s) in `subnets`. + The maximum number of entries allowed is 25. + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 25 + minItems: 1 + type: array + role: + description: |- + Role describes the network role in the pod. + + Allowed value is "Secondary". + Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. + enum: + - Primary + - Secondary + type: string + subnets: + description: |- + Subnets 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. + + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `ipam.mode` is `Disabled`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different + IP families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + required: + - role + type: object + x-kubernetes-validations: + - message: Subnets is required with ipam.mode is Enabled or unset + rule: has(self.ipam) && has(self.ipam.mode) && self.ipam.mode + != 'Enabled' || has(self.subnets) + - message: Subnets must be unset when ipam.mode is Disabled + rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode + != ''Disabled'' || !has(self.subnets)' + - message: Disabled ipam.mode is only supported for Secondary + network + rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode + != ''Disabled'' || self.role == ''Secondary''' + - message: JoinSubnets is only supported for Primary network + rule: '!has(self.joinSubnets) || has(self.role) && self.role + == ''Primary''' + - message: MTU should be greater than or equal to 1280 when IPv6 + subnet is used + rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, + isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280' + - message: defaultGatewayIPs is only supported for Primary network + rule: '!has(self.defaultGatewayIPs) || has(self.role) && self.role + == ''Primary''' + - message: defaultGatewayIPs must belong to one of the subnets + specified in the subnets field + rule: '!has(self.defaultGatewayIPs) || self.defaultGatewayIPs.all(ip, + self.subnets.exists(subnet, cidr(subnet).containsIP(ip)))' + - message: defaultGatewayIPs must be specified for all IP families + rule: '!has(self.defaultGatewayIPs) || size(self.defaultGatewayIPs) + == size(self.subnets)' + - message: reservedSubnets must be unset when subnets is unset + rule: '!has(self.reservedSubnets) || has(self.subnets)' + - message: reservedSubnets is only supported for Primary network + rule: '!has(self.reservedSubnets) || has(self.role) && self.role + == ''Primary''' + - message: infrastructureSubnets must be unset when subnets is + unset + rule: '!has(self.infrastructureSubnets) || has(self.subnets)' + - message: infrastructureSubnets is only supported for Primary + network + rule: '!has(self.infrastructureSubnets) || has(self.role) && + self.role == ''Primary''' + - message: defaultGatewayIPs have to belong to infrastructureSubnets + rule: '!has(self.infrastructureSubnets) || !has(self.defaultGatewayIPs) + || self.defaultGatewayIPs.all(ip, self.infrastructureSubnets.exists(subnet, + cidr(subnet).containsIP(ip)))' + - fieldPath: .reservedSubnets + message: reservedSubnets must be subnetworks of the networks + specified in the subnets field + rule: '!has(self.reservedSubnets) || self.reservedSubnets.all(e, + self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))' + - fieldPath: .infrastructureSubnets + message: infrastructureSubnets must be subnetworks of the networks + specified in the subnets field + rule: '!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(e, + self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))' + - message: infrastructureSubnets and reservedSubnets must not + overlap + rule: '!has(self.infrastructureSubnets) || !has(self.reservedSubnets) + || self.infrastructureSubnets.all(infra, !self.reservedSubnets.exists(reserved, + cidr(infra).containsCIDR(reserved) || cidr(reserved).containsCIDR(infra)))' + - message: infrastructureSubnets must be a masked network address + (no host bits set) + rule: '!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(s, + isCIDR(s) && cidr(s) == cidr(s).masked())' + - message: reservedSubnets must be a masked network address (no + host bits set) + rule: '!has(self.reservedSubnets) || self.reservedSubnets.all(s, + isCIDR(s) && cidr(s) == cidr(s).masked())' + layer3: + description: Layer3 is the Layer3 topology configuration. + 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: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different + IP families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + mtu: + description: |- + MTU is the maximum transmission unit for a network. + + MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. + format: int32 + maximum: 65536 + minimum: 576 + type: integer + role: + description: |- + Role describes the network role in the pod. + + Allowed values are "Primary" and "Secondary". + Primary network is automatically assigned to every pod created in the same namespace. + Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. + enum: + - Primary + - Secondary + type: string + subnets: + description: |- + Subnets 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: + properties: + cidr: + description: CIDR specifies L3Subnet, which is split + into smaller subnets for every node. + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + hostSubnet: + description: |- + HostSubnet specifies the subnet size for every node. + + When not set, it will be assigned automatically. + format: int32 + maximum: 127 + minimum: 1 + type: integer + required: + - cidr + type: object + x-kubernetes-validations: + - message: HostSubnet must be smaller than CIDR subnet + rule: '!has(self.hostSubnet) || !isCIDR(self.cidr) || + self.hostSubnet > cidr(self.cidr).prefixLength()' + - message: HostSubnet must < 32 for ipv4 CIDR + rule: '!has(self.hostSubnet) || !isCIDR(self.cidr) || + (cidr(self.cidr).ip().family() != 4 || self.hostSubnet + < 32)' + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different + IP families + rule: size(self) != 2 || !isCIDR(self[0].cidr) || !isCIDR(self[1].cidr) + || cidr(self[0].cidr).ip().family() != cidr(self[1].cidr).ip().family() + required: + - role + - subnets + type: object + x-kubernetes-validations: + - message: JoinSubnets is only supported for Primary network + rule: '!has(self.joinSubnets) || has(self.role) && self.role + == ''Primary''' + - message: MTU should be greater than or equal to 1280 when IPv6 + subnet is used + rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, + isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu + >= 1280' + localnet: + description: Localnet is the Localnet topology configuration. + properties: + excludeSubnets: + description: |- + excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`. + The CIDRs in this list must be in range of at least one subnet specified in `subnets`. + excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets` + are subject to assignment. + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. + When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses + (which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example: + Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned + to pods: `10.0.0.201`, `10.0.0.202`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 25 + minItems: 1 + type: array + ipam: + description: "ipam configurations for the network.\nipam is + optional. When omitted, `subnets` must be specified.\nWhen + `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode` + controls how much of the IP configuration will be managed + by OVN.\n When `Enabled`, OVN-Kubernetes will apply IP + configuration to the SDN infra and assign IPs from the selected\n + \ subnet to the pods.\n When `Disabled`, OVN-Kubernetes + only assigns MAC addresses, and provides layer2 communication, + and enables users\n to configure IP addresses on the pods.\n`ipam.lifecycle` + controls IP addresses management lifecycle.\n When set + to 'Persistent', the assigned IP addresses will be persisted + in `ipamclaims.k8s.cni.cncf.io` object.\n\t Useful for + VMs, IP address will be persistent after restarts and migrations. + Supported when `ipam.mode` is `Enabled`." + minProperties: 1 + properties: + lifecycle: + description: |- + Lifecycle controls IP addresses management lifecycle. + + The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an + `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested. + Only supported when mode is `Enabled`. + enum: + - Persistent + type: string + mode: + description: |- + Mode controls how much of the IP configuration will be managed by OVN. + `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs + from the selected subnet to the individual pods. + `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users + configure IP addresses for the pods. + `Disabled` is only available for Secondary networks. + By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function + (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network. + Defaults to `Enabled`. + enum: + - Enabled + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: lifecycle Persistent is only supported when ipam.mode + is Enabled + rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent'' + || !has(self.mode) || self.mode == ''Enabled''' + mtu: + description: |- + mtu is the maximum transmission unit for a network. + mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology) + is used for the network. + Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280. + Maximum value is 65536. + In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings, + this field enables configuring the same MTU on pod interface, having the pod MTU aligned with the network MTU. + Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions + and bad performance. + format: int32 + maximum: 65536 + minimum: 576 + type: integer + physicalNetworkName: + description: |- + physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required. + Min length is 1, max length is 253, cannot contain `,` or `:` characters. + In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP), + this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: physicalNetworkName cannot contain `,` or `:` characters + rule: self.matches('^[^,:]+$') + role: + description: |- + role describes the network role in the pod, required. + Controls whether the pod interface will act as primary or secondary. + Localnet topology supports `Secondary` only. + The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing + to subject. + enum: + - Secondary + type: string + subnets: + description: |- + subnets is a list of subnets used for pods in this localnet network across the cluster. + The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family. + When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod, + eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server). + subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically. + Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `ipam.mode` is `Disabled`. + When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services + (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids + conflicts with the existing IPAM services on this localnet network. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different + IP families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + vlan: + description: |- + vlan configuration for the network. + vlan.mode is the VLAN mode. + When "Access" is set, OVN-Kubernetes configures the network logical switch port in access mode. + vlan.access is the access VLAN configuration. + vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port. + vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`). + When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods. + properties: + access: + description: Access is the access VLAN configuration + properties: + id: + description: |- + id is the VLAN ID (VID) to be set for the network. + id should be higher than 0 and lower than 4095. + format: int32 + maximum: 4094 + minimum: 1 + type: integer + required: + - id + type: object + mode: + description: |- + mode describe the network VLAN mode. + Allowed value is "Access". + Access sets the network logical switch port in access mode, according to the config. + enum: + - Access + type: string + required: + - mode + type: object + x-kubernetes-validations: + - message: vlan access config is required when vlan mode is + 'Access', and forbidden otherwise + rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access): + !has(self.access)' + required: + - physicalNetworkName + - role + type: object + x-kubernetes-validations: + - message: Subnets is required with ipam.mode is Enabled or unset, + and forbidden otherwise + rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode + == ''Enabled'' ? has(self.subnets) : !has(self.subnets)' + - message: excludeSubnets must be unset when subnets is unset + rule: '!has(self.excludeSubnets) || has(self.subnets)' + - message: MTU should be greater than or equal to 1280 when an + IPv6 subnet is used + rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, + isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280' + - fieldPath: .excludeSubnets + message: excludeSubnets must be subnetworks of the networks + specified in the subnets field + rule: '!has(self.excludeSubnets) || self.excludeSubnets.all(e, + self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))' + topology: + description: |- + Topology describes network configuration. + + Allowed values are "Layer3", "Layer2" and "Localnet". + Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets. + Layer2 topology creates one logical switch shared by all nodes. + Localnet topology is based on layer 2 topology, but also allows connecting to an existent (configured) physical network to provide north-south traffic to the workloads. + enum: + - Layer2 + - Layer3 + - Localnet + type: string + required: + - topology + type: object + x-kubernetes-validations: + - message: spec.layer3 is required when topology is Layer3 and forbidden + otherwise + rule: 'has(self.topology) && self.topology == ''Layer3'' ? has(self.layer3): + !has(self.layer3)' + - message: spec.layer2 is required when topology is Layer2 and forbidden + otherwise + rule: 'has(self.topology) && self.topology == ''Layer2'' ? has(self.layer2): + !has(self.layer2)' + - message: spec.localnet is required when topology is Localnet and + forbidden otherwise + rule: 'has(self.topology) && self.topology == ''Localnet'' ? has(self.localnet): + !has(self.localnet)' + - message: Network spec is immutable + rule: self == oldSelf + required: + - namespaceSelector + - network + type: object + status: + description: ClusterUserDefinedNetworkStatus contains the observed status + of the ClusterUserDefinedNetwork. + properties: + conditions: + description: Conditions slice of condition objects indicating details + about ClusterUserDefineNetwork status. + 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 + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/assets/components/ovn/common/crd-ra.yaml b/assets/components/ovn/common/crd-ra.yaml new file mode 100644 index 0000000000..49ca2695c4 --- /dev/null +++ b/assets/components/ovn/common/crd-ra.yaml @@ -0,0 +1,631 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: routeadvertisements.k8s.ovn.org +spec: + group: k8s.ovn.org + names: + kind: RouteAdvertisements + listKind: RouteAdvertisementsList + plural: routeadvertisements + shortNames: + - ra + singular: routeadvertisements + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: RouteAdvertisements is the Schema for the routeadvertisements + API + 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: RouteAdvertisementsSpec defines the desired state of RouteAdvertisements + properties: + advertisements: + description: advertisements determines what is advertised. + items: + description: AdvertisementType determines the type of advertisement. + enum: + - PodNetwork + - EgressIP + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - rule: self.all(x, self.exists_one(y, x == y)) + frrConfigurationSelector: + description: |- + frrConfigurationSelector determines which FRRConfigurations will the + OVN-Kubernetes driven FRRConfigurations be based on. This field follows + standard label selector semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + networkSelectors: + description: |- + networkSelectors determines which network routes should be advertised. + Only ClusterUserDefinedNetworks and the default network can be selected. + items: + description: NetworkSelector selects a set of networks. + properties: + clusterUserDefinedNetworkSelector: + description: |- + clusterUserDefinedNetworkSelector selects ClusterUserDefinedNetworks when + NetworkSelectionType is 'ClusterUserDefinedNetworks'. + properties: + networkSelector: + description: |- + networkSelector selects ClusterUserDefinedNetworks by label. A null + selector will mot match anything, while an empty ({}) selector will match + all. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - networkSelector + type: object + networkAttachmentDefinitionSelector: + description: |- + networkAttachmentDefinitionSelector selects networks defined in the + selected NetworkAttachmentDefinitions when NetworkSelectionType is + 'SecondaryUserDefinedNetworks'. + properties: + namespaceSelector: + description: |- + namespaceSelector selects namespaces where the + NetworkAttachmentDefinitions are defined. This field follows standard + label selector semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + networkSelector: + description: |- + networkSelector selects NetworkAttachmentDefinitions within the selected + namespaces by label. This field follows standard label selector + semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - namespaceSelector + - networkSelector + type: object + networkSelectionType: + description: networkSelectionType determines the type of networks + selected. + enum: + - DefaultNetwork + - ClusterUserDefinedNetworks + - PrimaryUserDefinedNetworks + - SecondaryUserDefinedNetworks + - NetworkAttachmentDefinitions + type: string + primaryUserDefinedNetworkSelector: + description: |- + primaryUserDefinedNetworkSelector selects primary UserDefinedNetworks when + NetworkSelectionType is 'PrimaryUserDefinedNetworks'. + properties: + namespaceSelector: + description: |- + namespaceSelector select the primary UserDefinedNetworks that are servind + the selected namespaces. This field follows standard label selector + semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - namespaceSelector + type: object + secondaryUserDefinedNetworkSelector: + description: |- + secondaryUserDefinedNetworkSelector selects secondary UserDefinedNetworks + when NetworkSelectionType is 'SecondaryUserDefinedNetworks'. + properties: + namespaceSelector: + description: |- + namespaceSelector selects namespaces where the secondary + UserDefinedNetworks are defined. This field follows standard label + selector semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + networkSelector: + description: |- + networkSelector selects secondary UserDefinedNetworks within the selected + namespaces by label. This field follows standard label selector + semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - namespaceSelector + - networkSelector + type: object + required: + - networkSelectionType + type: object + x-kubernetes-validations: + - message: 'Inconsistent selector: both networkSelectionType ClusterUserDefinedNetworks + and clusterUserDefinedNetworkSelector have to be set or neither' + rule: '!has(self.networkSelectionType) ? true : has(self.clusterUserDefinedNetworkSelector) + ? self.networkSelectionType == ''ClusterUserDefinedNetworks'' + : self.networkSelectionType != ''ClusterUserDefinedNetworks''' + - message: 'Inconsistent selector: both networkSelectionType PrimaryUserDefinedNetworks + and primaryUserDefinedNetworkSelector have to be set or neither' + rule: '!has(self.networkSelectionType) ? true : has(self.primaryUserDefinedNetworkSelector) + ? self.networkSelectionType == ''PrimaryUserDefinedNetworks'' + : self.networkSelectionType != ''PrimaryUserDefinedNetworks''' + - message: 'Inconsistent selector: both networkSelectionType SecondaryUserDefinedNetworks + and secondaryUserDefinedNetworkSelector have to be set or neither' + rule: '!has(self.networkSelectionType) ? true : has(self.secondaryUserDefinedNetworkSelector) + ? self.networkSelectionType == ''SecondaryUserDefinedNetworks'' + : self.networkSelectionType != ''SecondaryUserDefinedNetworks''' + - message: 'Inconsistent selector: both networkSelectionType NetworkAttachmentDefinitions + and networkAttachmentDefinitionSelector have to be set or neither' + rule: '!has(self.networkSelectionType) ? true : has(self.networkAttachmentDefinitionSelector) + ? self.networkSelectionType == ''NetworkAttachmentDefinitions'' + : self.networkSelectionType != ''NetworkAttachmentDefinitions''' + maxItems: 5 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - networkSelectionType + x-kubernetes-list-type: map + nodeSelector: + description: |- + nodeSelector limits the advertisements to selected nodes. This field + follows standard label selector semantics. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + targetVRF: + description: targetVRF determines which VRF the routes should be advertised + in. + type: string + required: + - advertisements + - frrConfigurationSelector + - networkSelectors + - nodeSelector + type: object + x-kubernetes-validations: + - message: If 'PodNetwork' is selected for advertisement, a 'nodeSelector' + can't be specified as it needs to be advertised on all nodes + rule: (!has(self.nodeSelector.matchLabels) && !has(self.nodeSelector.matchExpressions)) + || !('PodNetwork' in self.advertisements) + - message: Only DefaultNetwork or ClusterUserDefinedNetworks can be selected + rule: '!self.networkSelectors.exists(i, i.networkSelectionType != ''DefaultNetwork'' + && i.networkSelectionType != ''ClusterUserDefinedNetworks'')' + status: + description: |- + RouteAdvertisementsStatus defines the observed state of RouteAdvertisements. + It should always be reconstructable from the state of the cluster and/or + outside world. + properties: + conditions: + description: |- + conditions is an array of condition objects indicating details about + status of RouteAdvertisements 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: |- + status is a concise indication of whether the RouteAdvertisements + resource is applied with success. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/assets/components/ovn/common/crd-udn.yaml b/assets/components/ovn/common/crd-udn.yaml new file mode 100644 index 0000000000..3e7f5e7586 --- /dev/null +++ b/assets/components/ovn/common/crd-udn.yaml @@ -0,0 +1,463 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: userdefinednetworks.k8s.ovn.org +spec: + group: k8s.ovn.org + names: + kind: UserDefinedNetwork + listKind: UserDefinedNetworkList + plural: userdefinednetworks + singular: userdefinednetwork + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: UserDefinedNetwork describe network request for a Namespace. + 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: UserDefinedNetworkSpec defines the desired state of UserDefinedNetworkSpec. + properties: + layer2: + description: Layer2 is the Layer2 topology configuration. + properties: + defaultGatewayIPs: + description: |- + defaultGatewayIPs specifies the default gateway IP used in the internal OVN topology. + + Dual-stack clusters may set 2 IPs (one for each IP family), otherwise only 1 IP 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, an IP from the subnets field is used. + items: + type: string + x-kubernetes-validations: + - message: IP is invalid + rule: isIP(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 IPs are set, they must be from different IP + families + rule: size(self) != 2 || !isIP(self[0]) || !isIP(self[1]) || + ip(self[0]).family() != ip(self[1]).family() + infrastructureSubnets: + description: |- + infrastructureSubnets specifies a list of internal CIDR ranges that OVN-Kubernetes will reserve for internal network infrastructure. + Any IP addresses within these ranges cannot be assigned to workloads. + When omitted, OVN-Kubernetes will automatically allocate IP addresses from `subnets` for its infrastructure needs. + When there are not enough available IPs in the provided infrastructureSubnets, OVN-Kubernetes will automatically allocate IP addresses from subnets for its infrastructure needs. + When `reservedSubnets` is also specified the CIDRs cannot overlap. + When `defaultGatewayIPs` is also specified, the default gateway IPs must belong to one of the infrastructure subnet CIDRs. + Each item should be in range of the specified CIDR(s) in `subnets`. + The maximum number of entries allowed is 4. + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 4 + minItems: 1 + type: array + ipam: + description: IPAM section contains IPAM-related configuration + for the network. + minProperties: 1 + properties: + lifecycle: + description: |- + Lifecycle controls IP addresses management lifecycle. + + The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an + `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested. + Only supported when mode is `Enabled`. + enum: + - Persistent + type: string + mode: + description: |- + Mode controls how much of the IP configuration will be managed by OVN. + `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs + from the selected subnet to the individual pods. + `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users + configure IP addresses for the pods. + `Disabled` is only available for Secondary networks. + By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function + (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network. + Defaults to `Enabled`. + enum: + - Enabled + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: lifecycle Persistent is only supported when ipam.mode + is Enabled + rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent'' + || !has(self.mode) || self.mode == ''Enabled''' + 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: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different IP + families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + mtu: + description: |- + MTU is the maximum transmission unit for a network. + MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. + format: int32 + maximum: 65536 + minimum: 576 + type: integer + reservedSubnets: + description: |- + reservedSubnets specifies a list of CIDRs reserved for static IP assignment, excluded from automatic allocation. + reservedSubnets is optional. When omitted, all IP addresses in `subnets` are available for automatic assignment. + IPs from these ranges can still be requested through static IP assignment. + Each item should be in range of the specified CIDR(s) in `subnets`. + The maximum number of entries allowed is 25. + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 25 + minItems: 1 + type: array + role: + description: |- + Role describes the network role in the pod. + + Allowed value is "Secondary". + Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. + enum: + - Primary + - Secondary + type: string + subnets: + description: |- + Subnets 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. + + The format should match standard CIDR notation (for example, "10.128.0.0/16"). + This field must be omitted if `ipam.mode` is `Disabled`. + items: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different IP + families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + required: + - role + type: object + x-kubernetes-validations: + - message: Subnets is required with ipam.mode is Enabled or unset + rule: has(self.ipam) && has(self.ipam.mode) && self.ipam.mode != + 'Enabled' || has(self.subnets) + - message: Subnets must be unset when ipam.mode is Disabled + rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode + != ''Disabled'' || !has(self.subnets)' + - message: Disabled ipam.mode is only supported for Secondary network + rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode + != ''Disabled'' || self.role == ''Secondary''' + - message: JoinSubnets is only supported for Primary network + rule: '!has(self.joinSubnets) || has(self.role) && self.role == + ''Primary''' + - message: MTU should be greater than or equal to 1280 when IPv6 subent + is used + rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, + isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280' + - message: defaultGatewayIPs is only supported for Primary network + rule: '!has(self.defaultGatewayIPs) || has(self.role) && self.role + == ''Primary''' + - message: defaultGatewayIPs must belong to one of the subnets specified + in the subnets field + rule: '!has(self.defaultGatewayIPs) || self.defaultGatewayIPs.all(ip, + self.subnets.exists(subnet, cidr(subnet).containsIP(ip)))' + - message: defaultGatewayIPs must be specified for all IP families + rule: '!has(self.defaultGatewayIPs) || size(self.defaultGatewayIPs) + == size(self.subnets)' + - message: reservedSubnets must be unset when subnets is unset + rule: '!has(self.reservedSubnets) || has(self.subnets)' + - message: reservedSubnets is only supported for Primary network + rule: '!has(self.reservedSubnets) || has(self.role) && self.role + == ''Primary''' + - message: infrastructureSubnets must be unset when subnets is unset + rule: '!has(self.infrastructureSubnets) || has(self.subnets)' + - message: infrastructureSubnets is only supported for Primary network + rule: '!has(self.infrastructureSubnets) || has(self.role) && self.role + == ''Primary''' + - message: defaultGatewayIPs have to belong to infrastructureSubnets + rule: '!has(self.infrastructureSubnets) || !has(self.defaultGatewayIPs) + || self.defaultGatewayIPs.all(ip, self.infrastructureSubnets.exists(subnet, + cidr(subnet).containsIP(ip)))' + - fieldPath: .reservedSubnets + message: reservedSubnets must be subnetworks of the networks specified + in the subnets field + rule: '!has(self.reservedSubnets) || self.reservedSubnets.all(e, + self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))' + - fieldPath: .infrastructureSubnets + message: infrastructureSubnets must be subnetworks of the networks + specified in the subnets field + rule: '!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(e, + self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))' + - message: infrastructureSubnets and reservedSubnets must not overlap + rule: '!has(self.infrastructureSubnets) || !has(self.reservedSubnets) + || self.infrastructureSubnets.all(infra, !self.reservedSubnets.exists(reserved, + cidr(infra).containsCIDR(reserved) || cidr(reserved).containsCIDR(infra)))' + - message: infrastructureSubnets must be a masked network address + (no host bits set) + rule: '!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(s, + isCIDR(s) && cidr(s) == cidr(s).masked())' + - message: reservedSubnets must be a masked network address (no + host bits set) + rule: '!has(self.reservedSubnets) || self.reservedSubnets.all(s, + isCIDR(s) && cidr(s) == cidr(s).masked())' + layer3: + description: Layer3 is the Layer3 topology configuration. + 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: + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different IP + families + rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1]) + || cidr(self[0]).ip().family() != cidr(self[1]).ip().family() + mtu: + description: |- + MTU is the maximum transmission unit for a network. + + MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. + format: int32 + maximum: 65536 + minimum: 576 + type: integer + role: + description: |- + Role describes the network role in the pod. + + Allowed values are "Primary" and "Secondary". + Primary network is automatically assigned to every pod created in the same namespace. + Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. + enum: + - Primary + - Secondary + type: string + subnets: + description: |- + Subnets 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: + properties: + cidr: + description: CIDR specifies L3Subnet, which is split into + smaller subnets for every node. + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR is invalid + rule: isCIDR(self) + hostSubnet: + description: |- + HostSubnet specifies the subnet size for every node. + + When not set, it will be assigned automatically. + format: int32 + maximum: 127 + minimum: 1 + type: integer + required: + - cidr + type: object + x-kubernetes-validations: + - message: HostSubnet must be smaller than CIDR subnet + rule: '!has(self.hostSubnet) || !isCIDR(self.cidr) || self.hostSubnet + > cidr(self.cidr).prefixLength()' + - message: HostSubnet must < 32 for ipv4 CIDR + rule: '!has(self.hostSubnet) || !isCIDR(self.cidr) || (cidr(self.cidr).ip().family() + != 4 || self.hostSubnet < 32)' + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-validations: + - message: When 2 CIDRs are set, they must be from different IP + families + rule: size(self) != 2 || !isCIDR(self[0].cidr) || !isCIDR(self[1].cidr) + || cidr(self[0].cidr).ip().family() != cidr(self[1].cidr).ip().family() + required: + - role + - subnets + type: object + x-kubernetes-validations: + - message: JoinSubnets is only supported for Primary network + rule: '!has(self.joinSubnets) || has(self.role) && self.role == + ''Primary''' + - message: MTU should be greater than or equal to 1280 when IPv6 subent + is used + rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, + isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu + >= 1280' + topology: + description: |- + Topology describes network configuration. + + Allowed values are "Layer3", "Layer2". + Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets. + Layer2 topology creates one logical switch shared by all nodes. + enum: + - Layer2 + - Layer3 + type: string + required: + - topology + type: object + x-kubernetes-validations: + - message: Spec is immutable + rule: self == oldSelf + - message: spec.layer3 is required when topology is Layer3 and forbidden + otherwise + rule: 'has(self.topology) && self.topology == ''Layer3'' ? has(self.layer3): + !has(self.layer3)' + - message: spec.layer2 is required when topology is Layer2 and forbidden + otherwise + rule: 'has(self.topology) && self.topology == ''Layer2'' ? has(self.layer2): + !has(self.layer2)' + status: + description: UserDefinedNetworkStatus contains the observed status of + the UserDefinedNetwork. + properties: + conditions: + 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 + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} \ No newline at end of file diff --git a/assets/components/ovn/common/role-sbdb.yaml b/assets/components/ovn/common/role-sbdb.yaml new file mode 100644 index 0000000000..545a619cf4 --- /dev/null +++ b/assets/components/ovn/common/role-sbdb.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: openshift-ovn-kubernetes-sbdb + namespace: openshift-ovn-kubernetes +rules: +- apiGroups: [""] + resources: + - endpoints + verbs: + - create + - update + - patch diff --git a/assets/components/ovn/common/role.yaml b/assets/components/ovn/common/role.yaml index 72d2555d25..b746f3f88b 100644 --- a/assets/components/ovn/common/role.yaml +++ b/assets/components/ovn/common/role.yaml @@ -19,19 +19,4 @@ rules: - get - delete - update - - list - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: openshift-ovn-kubernetes-sbdb - namespace: openshift-ovn-kubernetes -rules: -- apiGroups: [""] - resources: - - endpoints - verbs: - - create - - update - - patch + - list \ No newline at end of file diff --git a/assets/components/ovn/common/rolebinding-sbdb.yaml b/assets/components/ovn/common/rolebinding-sbdb.yaml new file mode 100644 index 0000000000..3468dd5d60 --- /dev/null +++ b/assets/components/ovn/common/rolebinding-sbdb.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: openshift-ovn-kubernetes-sbdb + namespace: openshift-ovn-kubernetes +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: openshift-ovn-kubernetes-sbdb +subjects: +- kind: ServiceAccount + name: ovn-kubernetes-controller + namespace: openshift-ovn-kubernetes \ No newline at end of file diff --git a/assets/components/ovn/common/rolebinding.yaml b/assets/components/ovn/common/rolebinding.yaml index 6df872e63f..a44b752fc1 100644 --- a/assets/components/ovn/common/rolebinding.yaml +++ b/assets/components/ovn/common/rolebinding.yaml @@ -12,18 +12,3 @@ subjects: - kind: ServiceAccount name: ovn-kubernetes-node namespace: openshift-ovn-kubernetes - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: openshift-ovn-kubernetes-sbdb - namespace: openshift-ovn-kubernetes -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: openshift-ovn-kubernetes-sbdb -subjects: -- kind: ServiceAccount - name: ovn-kubernetes-controller - namespace: openshift-ovn-kubernetes diff --git a/assets/components/ovn/multi-node/master/daemonset.yaml b/assets/components/ovn/multi-node/master/daemonset.yaml index c968672326..213eccfa40 100644 --- a/assets/components/ovn/multi-node/master/daemonset.yaml +++ b/assets/components/ovn/multi-node/master/daemonset.yaml @@ -377,6 +377,7 @@ spec: echo "I$(date "+%m%d %H:%M:%S.%N") - ovnkube-master - start ovnkube --init-master ${K8S_NODE}" exec /usr/bin/ovnkube \ --init-master "${K8S_NODE}" \ + --enable-interconnect \ --nb-address "{{.OVN_NB_DB_LIST}}" \ --sb-address "{{.OVN_SB_DB_LIST}}" \ --config-file=/run/ovnkube-config/ovnkube.conf \ @@ -384,8 +385,14 @@ spec: ${gateway_mode_flags} \ --inactivity-probe="180000" \ --enable-multicast \ + --enable-network-segmentation=true \ + --enable-multi-network=true \ + --enable-route-advertisements \ --disable-snat-multiple-gws \ - --acl-logging-rate-limit "20" + --acl-logging-rate-limit "20" \ + --gateway-v4-masquerade-subnet 169.254.0.0/17 \ + --gateway-v6-masquerade-subnet fd69::/112 + volumeMounts: # for checking ovs-configuration service - mountPath: /etc/systemd/system diff --git a/assets/components/ovn/single-node/master/daemonset.yaml b/assets/components/ovn/single-node/master/daemonset.yaml index 2d698042a3..9f7c029214 100644 --- a/assets/components/ovn/single-node/master/daemonset.yaml +++ b/assets/components/ovn/single-node/master/daemonset.yaml @@ -296,8 +296,9 @@ spec: terminationMessagePolicy: FallbackToLogsOnError # ovnkube master: convert kubernetes objects in to nbdb logical network components - - name: ovnkube-master - image: {{ .ReleaseImage.ovn_kubernetes_microshift }} + - name: ovnkube-controller + image: localhost/ovn-daemonset-fedora:latest + imagePullPolicy: Never command: - /bin/bash - -c @@ -339,6 +340,7 @@ spec: exec /usr/bin/ovnkube \ --init-master "${K8S_NODE}" \ --init-node "${K8S_NODE}" \ + --enable-interconnect \ --allow-no-uplink \ --config-file=/run/ovnkube-config/ovnkube.conf \ --loglevel "${OVN_KUBE_LOG_LEVEL}" \ @@ -348,8 +350,12 @@ spec: --nb-address "" \ --sb-address "" \ --enable-multicast \ - --disable-snat-multiple-gws \ --single-node \ + --enable-network-segmentation=true \ + --enable-multi-network=true \ + --enable-route-advertisements \ + --gateway-v4-masquerade-subnet 169.254.0.0/17 \ + --gateway-v6-masquerade-subnet fd69::/112 \ --acl-logging-rate-limit "20" lifecycle: preStop: @@ -396,6 +402,11 @@ spec: name: etc-openvswitch-node - mountPath: /etc/ovn/ name: etc-openvswitch-node + - mountPath: /run/systemd/private + mountPropagation: HostToContainer + name: run-systemd + readOnly: true + subPath: private resources: requests: cpu: 10m @@ -455,6 +466,10 @@ spec: - name: host-cni-bin hostPath: path: "/run/cni/bin" + - hostPath: + path: /run/systemd + type: "" + name: run-systemd - name: kubeconfig hostPath: diff --git a/assets/optional/udn/frr-k8s/frr-k8s.yaml b/assets/optional/udn/frr-k8s/frr-k8s.yaml new file mode 100644 index 0000000000..5774741bb0 --- /dev/null +++ b/assets/optional/udn/frr-k8s/frr-k8s.yaml @@ -0,0 +1,1581 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/component: frr-k8s + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: system + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: namespace + app.kubernetes.io/part-of: frr-k8s + control-plane: frr-k8s + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/warn: privileged + name: frr-k8s-system +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: bgpsessionstates.frrk8s.metallb.io +spec: + group: frrk8s.metallb.io + names: + kind: BGPSessionState + listKind: BGPSessionStateList + plural: bgpsessionstates + singular: bgpsessionstate + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.node + name: Node + type: string + - jsonPath: .status.peer + name: Peer + type: string + - jsonPath: .status.vrf + name: VRF + type: string + - jsonPath: .status.bgpStatus + name: BGP + type: string + - jsonPath: .status.bfdStatus + name: BFD + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BGPSessionState exposes the status of a BGP Session from the + FRR instance running on the node. + 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: BGPSessionStateSpec defines the desired state of BGPSessionState. + type: object + status: + description: BGPSessionStateStatus defines the observed state of BGPSessionState. + properties: + bfdStatus: + type: string + bgpStatus: + type: string + node: + type: string + peer: + type: string + vrf: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: frrconfigurations.frrk8s.metallb.io +spec: + group: frrk8s.metallb.io + names: + kind: FRRConfiguration + listKind: FRRConfigurationList + plural: frrconfigurations + singular: frrconfiguration + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: FRRConfiguration is a piece of FRR configuration. + 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: FRRConfigurationSpec defines the desired state of FRRConfiguration. + properties: + bgp: + description: BGP is the configuration related to the BGP protocol. + properties: + bfdProfiles: + description: BFDProfiles is the list of bfd profiles to be used + when configuring the neighbors. + items: + description: |- + BFDProfile is the configuration related to the BFD protocol associated + to a BGP session. + properties: + detectMultiplier: + description: |- + Configures the detection multiplier to determine + packet loss. The remote transmission interval will be multiplied + by this value to determine the connection loss detection timer. + format: int32 + maximum: 255 + minimum: 2 + type: integer + echoInterval: + description: |- + Configures the minimal echo receive transmission + interval that this system is capable of handling in milliseconds. + Defaults to 50ms + format: int32 + maximum: 60000 + minimum: 10 + type: integer + echoMode: + description: |- + Enables or disables the echo transmission mode. + This mode is disabled by default, and not supported on multi + hops setups. + type: boolean + minimumTtl: + description: |- + For multi hop sessions only: configure the minimum + expected TTL for an incoming BFD control packet. + format: int32 + maximum: 254 + minimum: 1 + type: integer + name: + description: |- + The name of the BFD Profile to be referenced in other parts + of the configuration. + type: string + passiveMode: + description: |- + Mark session as passive: a passive session will not + attempt to start the connection and will wait for control packets + from peer before it begins replying. + type: boolean + receiveInterval: + description: |- + The minimum interval that this system is capable of + receiving control packets in milliseconds. + Defaults to 300ms. + format: int32 + maximum: 60000 + minimum: 10 + type: integer + transmitInterval: + description: |- + The minimum transmission interval (less jitter) + that this system wants to use to send BFD control packets in + milliseconds. Defaults to 300ms + format: int32 + maximum: 60000 + minimum: 10 + type: integer + required: + - name + type: object + type: array + routers: + description: Routers is the list of routers we want FRR to configure + (one per VRF). + items: + description: Router represent a neighbor router we want FRR + to connect to. + properties: + asn: + description: ASN is the AS number to use for the local end + of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + id: + description: ID is the BGP router ID + type: string + imports: + description: Imports is the list of imported VRFs we want + for this router / vrf. + items: + description: Import represents the possible imported VRFs + to a given router. + properties: + vrf: + description: Vrf is the vrf we want to import from + type: string + type: object + type: array + neighbors: + description: Neighbors is the list of neighbors we want + to establish BGP sessions with. + items: + description: Neighbor represents a BGP Neighbor we want + FRR to connect to. + properties: + address: + description: Address is the IP address to establish + the session with. + type: string + asn: + description: |- + ASN is the AS number to use for the local end of the session. + ASN and DynamicASN are mutually exclusive and one of them must be specified. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + bfdProfile: + description: |- + BFDProfile is the name of the BFD Profile to be used for the BFD session associated + to the BGP session. If not set, the BFD session won't be set up. + type: string + connectTime: + description: Requested BGP connect time, controls + how long BGP waits between connection attempts to + a neighbor. + type: string + x-kubernetes-validations: + - message: connect time should be between 1 seconds + to 65535 + rule: duration(self).getSeconds() >= 1 && duration(self).getSeconds() + <= 65535 + - message: connect time should contain a whole number + of seconds + rule: duration(self).getMilliseconds() % 1000 == + 0 + disableMP: + default: false + description: |- + DisableMP is no longer used and has no effect. + Use DualStackAddressFamily instead to enable the neighbor for both IPv4 and IPv6 address families. + + + Deprecated: This field is ignored. Use DualStackAddressFamily instead. + type: boolean + dualStackAddressFamily: + default: false + description: |- + To set if we want to enable the neighbor not only for the ipfamily related to its session, + but also the other one. This allows to advertise/receive IPv4 prefixes over IPv6 sessions and vice versa. + type: boolean + dynamicASN: + description: |- + DynamicASN detects the AS number to use for the local end of the session + without explicitly setting it via the ASN field. Limited to: + internal - if the neighbor's ASN is different than the router's the connection is denied. + external - if the neighbor's ASN is the same as the router's the connection is denied. + ASN and DynamicASN are mutually exclusive and one of them must be specified. + enum: + - internal + - external + type: string + ebgpMultiHop: + description: EBGPMultiHop indicates if the BGPPeer + is multi-hops away. + type: boolean + enableGracefulRestart: + description: |- + EnableGracefulRestart allows BGP peer to continue to forward data packets along + known routes while the routing protocol information is being restored. If + the session is already established, the configuration will have effect + after reconnecting to the peer + type: boolean + holdTime: + description: |- + HoldTime is the requested BGP hold time, per RFC4271. + Defaults to 180s. + type: string + interface: + description: |- + Interface is the node interface over which the unnumbered BGP peering will + be established. No API validation takes place as that string value + represents an interface name on the host and if user provides an invalid + value, only the actual BGP session will not be established. + Address and Interface are mutually exclusive and one of them must be specified. + Note: when enabling unnumbered, the neighbor will be enabled for both + IPv4 and IPv6 address families. + type: string + keepaliveTime: + description: |- + KeepaliveTime is the requested BGP keepalive time, per RFC4271. + Defaults to 60s. + type: string + password: + description: |- + Password to be used for establishing the BGP session. + Password and PasswordSecret are mutually exclusive. + type: string + passwordSecret: + description: |- + PasswordSecret is name of the authentication secret for the neighbor. + the secret must be of type "kubernetes.io/basic-auth", and created in the + same namespace as the frr-k8s daemon. The password is stored in the + secret as the key "password". + Password and PasswordSecret are mutually exclusive. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + port: + description: |- + Port is the port to dial when establishing the session. + Defaults to 179. + maximum: 16384 + minimum: 0 + type: integer + sourceaddress: + description: |- + SourceAddress is the IPv4 or IPv6 source address to use for the BGP + session to this neighbour, may be specified as either an IP address + directly or as an interface name + type: string + toAdvertise: + description: |- + ToAdvertise represents the list of prefixes to advertise to the given neighbor + and the associated properties. + properties: + allowed: + description: |- + Allowed is is the list of prefixes allowed to be propagated to + this neighbor. They must match the prefixes defined in the router. + properties: + mode: + default: filtered + description: |- + Mode is the mode to use when handling the prefixes. + When set to "filtered", only the prefixes in the given list will be allowed. + When set to "all", all the prefixes configured on the router will be allowed. + enum: + - all + - filtered + type: string + prefixes: + items: + type: string + type: array + type: object + withCommunity: + description: |- + PrefixesWithCommunity is a list of prefixes that are associated to a + bgp community when being advertised. The prefixes associated to a given local pref + must be in the prefixes allowed to be advertised. + items: + description: CommunityPrefixes is a list of + prefixes associated to a community. + properties: + community: + description: Community is the community + associated to the prefixes. + type: string + prefixes: + description: Prefixes is the list of prefixes + associated to the community. + format: cidr + items: + type: string + minItems: 1 + type: array + type: object + type: array + withLocalPref: + description: |- + PrefixesWithLocalPref is a list of prefixes that are associated to a local + preference when being advertised. The prefixes associated to a given local pref + must be in the prefixes allowed to be advertised. + items: + description: LocalPrefPrefixes is a list of + prefixes associated to a local preference. + properties: + localPref: + description: LocalPref is the local preference + associated to the prefixes. + format: int32 + type: integer + prefixes: + description: Prefixes is the list of prefixes + associated to the local preference. + format: cidr + items: + type: string + minItems: 1 + type: array + type: object + type: array + type: object + toReceive: + description: ToReceive represents the list of prefixes + to receive from the given neighbor. + properties: + allowed: + description: |- + Allowed is the list of prefixes allowed to be received from + this neighbor. + properties: + mode: + default: filtered + description: |- + Mode is the mode to use when handling the prefixes. + When set to "filtered", only the prefixes in the given list will be allowed. + When set to "all", all the prefixes configured on the router will be allowed. + enum: + - all + - filtered + type: string + prefixes: + items: + description: PrefixSelector is a filter + of prefixes to receive. + properties: + ge: + description: |- + The prefix length modifier. This selector accepts any matching prefix with length + greater or equal the given value. + format: int32 + maximum: 128 + minimum: 1 + type: integer + le: + description: |- + The prefix length modifier. This selector accepts any matching prefix with length + less or equal the given value. + format: int32 + maximum: 128 + minimum: 1 + type: integer + prefix: + format: cidr + type: string + type: object + type: array + type: object + type: object + type: object + type: array + prefixes: + description: Prefixes is the list of prefixes we want to + advertise from this router instance. + items: + type: string + type: array + vrf: + description: VRF is the host vrf used to establish sessions + from this router. + type: string + required: + - asn + type: object + type: array + type: object + nodeSelector: + description: |- + NodeSelector limits the nodes that will attempt to apply this config. + When specified, the configuration will be considered only on nodes + whose labels match the specified selectors. + When it is not specified all nodes will attempt to apply this config. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + raw: + description: |- + Raw is a snippet of raw frr configuration that gets appended to the + one rendered translating the type safe API. + properties: + priority: + description: |- + Priority is the order with this configuration is appended to the + bottom of the rendered configuration. A higher value means the + raw config is appended later in the configuration file. + type: integer + rawConfig: + description: |- + Config is a raw FRR configuration to be appended to the configuration + rendered via the k8s api. + type: string + type: object + type: object + status: + description: FRRConfigurationStatus defines the observed state of FRRConfiguration. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: frrk8sconfigurations.frrk8s.metallb.io +spec: + group: frrk8s.metallb.io + names: + kind: FRRK8sConfiguration + listKind: FRRK8sConfigurationList + plural: frrk8sconfigurations + singular: frrk8sconfiguration + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: |- + FRRK8sConfiguration holds the FRR Operator configuration with global + settings for the K8s and FRR. + 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: FRRK8sConfigurationSpec defines the desired state of FRRK8sConfiguration. + properties: + logLevel: + description: |- + LogLevel sets the logging verbosity for the FRR-K8s components at runtime. + When configured, this value overrides the defaults established by the --log-level CLI flag. + Valid values are: all, debug, info, warn, error, none. + enum: + - all + - debug + - info + - warn + - error + - none + type: string + type: object + status: + description: FRRK8sConfigurationStatus defines the observed state of FRRK8sConfiguration. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: frrnodestates.frrk8s.metallb.io +spec: + group: frrk8s.metallb.io + names: + kind: FRRNodeState + listKind: FRRNodeStateList + plural: frrnodestates + singular: frrnodestate + scope: Cluster + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: FRRNodeState exposes the status of the FRR instance running on + each node. + 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: FRRNodeStateSpec defines the desired state of FRRNodeState. + type: object + status: + description: FRRNodeStateStatus defines the observed state of FRRNodeState. + properties: + lastConversionResult: + description: LastConversionResult is the status of the last translation + between the `FRRConfiguration`s resources and FRR's configuration, + contains "success" or an error. + type: string + lastReloadResult: + description: LastReloadResult represents the status of the last configuration + update operation by FRR, contains "success" or an error. + type: string + runningConfig: + description: RunningConfig represents the current FRR running config, + which is the configuration the FRR instance is currently running + with. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s-sa + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-daemon + namespace: frr-k8s-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app: frr-k8s + name: frr-k8s-daemon-role + namespace: frr-k8s-system +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - update +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-nodestate-cleaner-role + namespace: frr-k8s-system +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - frrk8s.metallb.io + resources: + - frrnodestates + verbs: + - get + - list + - watch + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: frr-k8s-daemon-role +rules: +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - list + - watch +- apiGroups: + - admissionregistration.k8s.io + resourceNames: + - frr-k8s-validating-webhook-configuration + resources: + - validatingwebhookconfigurations + verbs: + - update +- apiGroups: + - frrk8s.metallb.io + resources: + - bgpsessionstates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - frrk8s.metallb.io + resources: + - bgpsessionstates/status + verbs: + - get + - patch + - update +- apiGroups: + - frrk8s.metallb.io + resources: + - frrconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - frrk8s.metallb.io + resources: + - frrconfigurations/finalizers + verbs: + - update +- apiGroups: + - frrk8s.metallb.io + resources: + - frrconfigurations/status + verbs: + - get + - patch + - update +- apiGroups: + - frrk8s.metallb.io + resources: + - frrk8sconfigurations + verbs: + - get + - list + - watch +- apiGroups: + - frrk8s.metallb.io + resources: + - frrnodestates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - frrk8s.metallb.io + resources: + - frrnodestates/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: metrics-reader + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: proxy-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: rolebinding + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-daemon-rolebinding + namespace: frr-k8s-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: frr-k8s-daemon-role +subjects: +- kind: ServiceAccount + name: frr-k8s-daemon + namespace: frr-k8s-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: rolebinding + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-nodestate-cleaner-rolebinding + namespace: frr-k8s-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: frr-k8s-nodestate-cleaner-role +subjects: +- kind: ServiceAccount + name: frr-k8s-daemon + namespace: frr-k8s-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-daemon-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: frr-k8s-daemon-role +subjects: +- kind: ServiceAccount + name: frr-k8s-daemon + namespace: frr-k8s-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: proxy-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: frr-k8s + name: frr-k8s-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: frr-k8s-proxy-role +subjects: +- kind: ServiceAccount + name: frr-k8s-daemon + namespace: frr-k8s-system +--- +apiVersion: v1 +data: + daemons: | + # This file tells the frr package which daemons to start. + # + # Sample configurations for these daemons can be found in + # /usr/share/doc/frr/examples/. + # + # ATTENTION: + # + # When activating a daemon for the first time, a config file, even if it is + # empty, has to be present *and* be owned by the user and group "frr", else + # the daemon will not be started by /etc/init.d/frr. The permissions should + # be u=rw,g=r,o=. + # When using "vtysh" such a config file is also needed. It should be owned by + # group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too. + # + # The watchfrr and zebra daemons are always started. + # + bgpd=yes + ospfd=no + ospf6d=no + ripd=no + ripngd=no + isisd=no + pimd=no + ldpd=no + nhrpd=no + eigrpd=no + babeld=no + sharpd=no + pbrd=no + bfdd=yes + fabricd=no + vrrpd=no + + # + # If this option is set the /etc/init.d/frr script automatically loads + # the config via "vtysh -b" when the servers are started. + # Check /etc/pam.d/frr if you intend to use "vtysh"! + # + vtysh_enable=yes + zebra_options=" -A 127.0.0.1 -K 120 -s 90000000 --limit-fds 100000" + bgpd_options=" -A 127.0.0.1 -p 0 --limit-fds 100000" + ospfd_options=" -A 127.0.0.1" + ospf6d_options=" -A ::1" + ripd_options=" -A 127.0.0.1" + ripngd_options=" -A ::1" + isisd_options=" -A 127.0.0.1" + pimd_options=" -A 127.0.0.1" + ldpd_options=" -A 127.0.0.1" + nhrpd_options=" -A 127.0.0.1" + eigrpd_options=" -A 127.0.0.1" + babeld_options=" -A 127.0.0.1" + sharpd_options=" -A 127.0.0.1" + pbrd_options=" -A 127.0.0.1" + staticd_options="-A 127.0.0.1 --limit-fds 100000" + bfdd_options=" -A 127.0.0.1 --limit-fds 100000" + fabricd_options="-A 127.0.0.1" + vrrpd_options=" -A 127.0.0.1" + + # configuration profile + # + #frr_profile="traditional" + #frr_profile="datacenter" + + # + # This is the maximum number of FD's that will be available. + # Upon startup this is read by the control files and ulimit + # is called. Uncomment and use a reasonable value for your + # setup if you are expecting a large number of peers in + # say BGP. + #MAX_FDS=1024 + + # The list of daemons to watch is automatically generated by the init script. + #watchfrr_options="" + + # for debugging purposes, you can specify a "wrap" command to start instead + # of starting the daemon directly, e.g. to use valgrind on ospfd: + # ospfd_wrap="/usr/bin/valgrind" + # or you can use "all_wrap" for all daemons, e.g. to use perf record: + # all_wrap="/usr/bin/perf record --call-graph -" + # the normal daemon command is added to this at the end. + frr.conf: | + ! This file gets overriden the first time the speaker renders a config. + ! So anything configured here is only temporary. + frr version 8.0 + frr defaults traditional + hostname Router + line vty + log stdout informational + vtysh.conf: | + service integrated-vtysh-config +kind: ConfigMap +metadata: + name: frr-k8s-frr-startup + namespace: frr-k8s-system +--- +apiVersion: v1 +kind: Secret +metadata: + name: frr-k8s-webhook-server-cert + namespace: frr-k8s-system +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s-metrics-service + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: service + app.kubernetes.io/part-of: frr-k8s + control-plane: frr-k8s + name: frr-k8s-metrics-service + namespace: frr-k8s-system +spec: + ports: + - name: metricshttps + port: 9140 + targetPort: metricshttps + - name: frrmetricshttps + port: 9141 + targetPort: frrmetricshttps + selector: + control-plane: frr-k8s +--- +apiVersion: v1 +kind: Service +metadata: + name: frr-k8s-webhook-service + namespace: frr-k8s-system +spec: + ports: + - port: 443 + targetPort: webhook + selector: + control-plane: statuscleaner +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: statuscleaner + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: frr-k8s + app.kubernetes.io/part-of: frr-k8s + control-plane: statuscleaner + name: frr-k8s-statuscleaner + namespace: frr-k8s-system +spec: + selector: + matchLabels: + control-plane: statuscleaner + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: frr-k8s-webhook-server + labels: + app: frr-k8s-webhook-server + app.kubernetes.io/component: statuscleaner + control-plane: statuscleaner + spec: + containers: + - args: + - --log-level=info + - --namespace=$(NAMESPACE) + command: + - /statuscleaner + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: quay.io/metallb/frr-k8s:main + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /healthz + port: webhook + scheme: HTTPS + initialDelaySeconds: 15 + periodSeconds: 20 + name: frr-k8s-statuscleaner + ports: + - containerPort: 19443 + name: webhook + readinessProbe: + httpGet: + path: /healthz + port: webhook + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + hostNetwork: true + serviceAccountName: frr-k8s-daemon + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: frr-k8s-webhook-server-cert +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app.kubernetes.io/component: frr-k8s + app.kubernetes.io/created-by: frr-k8s + app.kubernetes.io/instance: frr-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: frr-k8s + app.kubernetes.io/part-of: frr-k8s + control-plane: frr-k8s + name: frr-k8s-daemon + namespace: frr-k8s-system +spec: + selector: + matchLabels: + control-plane: frr-k8s + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: frr-k8s + labels: + app: frr-k8s + app.kubernetes.io/component: frr-k8s + control-plane: frr-k8s + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + containers: + - args: + - --secure-listen-address=0.0.0.0:9140 + - --upstream=http://127.0.0.1:7572/ + - --logtostderr=true + - --v=0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 + name: kube-rbac-proxy + ports: + - containerPort: 9140 + name: metricshttps + protocol: TCP + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - args: + - --secure-listen-address=0.0.0.0:9141 + - --upstream=http://127.0.0.1:7573/ + - --logtostderr=true + - --v=0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 + name: kube-rbac-proxy-frr + ports: + - containerPort: 9141 + name: frrmetricshttps + protocol: TCP + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - args: + - --metrics-bind-address=127.0.0.1:7572 + - --node-name=$(NODE_NAME) + - --namespace=$(NAMESPACE) + - --log-level=info + command: + - /frr-k8s + env: + - name: FRR_CONFIG_FILE + value: /etc/frr_reloader/frr.conf + - name: FRR_RELOADER_PID_FILE + value: /etc/frr_reloader/reloader.pid + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: quay.io/metallb/frr-k8s:main + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + host: 127.0.0.1 + path: /metrics + port: monitoring + initialDelaySeconds: 15 + periodSeconds: 20 + name: controller + ports: + - containerPort: 7572 + name: monitoring + readinessProbe: + httpGet: + host: 127.0.0.1 + path: /metrics + port: monitoring + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /etc/frr_reloader + name: reloader + - command: + - /bin/sh + - -c + - /sbin/tini -- /usr/lib/frr/docker-start + env: + - name: TINI_SUBREAPER + value: "true" + image: quay.io/frrouting/frr:10.4.1 + livenessProbe: + failureThreshold: 3 + httpGet: + host: 127.0.0.1 + path: /livez + port: 7573 + periodSeconds: 5 + name: frr + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_ADMIN + - NET_RAW + - SYS_ADMIN + - NET_BIND_SERVICE + readOnlyRootFilesystem: true + startupProbe: + failureThreshold: 30 + httpGet: + host: 127.0.0.1 + path: /livez + port: 7573 + periodSeconds: 5 + volumeMounts: + - mountPath: /var/run/frr + name: frr-sockets + - mountPath: /etc/frr + name: frr-conf + - mountPath: /var/lib/frr + name: frr-lib + - mountPath: /var/tmp/frr + name: frr-tmp + - args: + - --metrics-port=7573 + - --metrics-bind-address=127.0.0.1 + command: + - /etc/frr_metrics/frr-metrics + image: quay.io/frrouting/frr:10.4.1 + name: frr-metrics + ports: + - containerPort: 7573 + name: frr-metrics + volumeMounts: + - mountPath: /var/run/frr + name: frr-sockets + - mountPath: /etc/frr + name: frr-conf + - mountPath: /etc/frr_metrics + name: metrics + - args: + - --node-name=$(NODE_NAME) + - --namespace=$(NAMESPACE) + - --pod-name=$(POD_NAME) + - --log-level=info + command: + - /etc/frr_status/frr-status + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: quay.io/frrouting/frr:10.4.1 + name: frr-status + volumeMounts: + - mountPath: /var/run/frr + name: frr-sockets + - mountPath: /etc/frr + name: frr-conf + - mountPath: /etc/frr_status + name: frr-status + - command: + - /etc/frr_reloader/frr-reloader.sh + image: quay.io/frrouting/frr:10.4.1 + name: reloader + volumeMounts: + - mountPath: /var/run/frr + name: frr-sockets + - mountPath: /etc/frr + name: frr-conf + - mountPath: /etc/frr_reloader + name: reloader + hostNetwork: true + initContainers: + - command: + - /bin/sh + - -c + - cp -rLf /tmp/frr/* /etc/frr/ + image: quay.io/frrouting/frr:10.4.1 + name: cp-frr-files + securityContext: + runAsGroup: 101 + runAsUser: 100 + volumeMounts: + - mountPath: /tmp/frr + name: frr-startup + - mountPath: /etc/frr + name: frr-conf + - command: + - /bin/sh + - -c + - cp -f /frr-reloader.sh /etc/frr_reloader/ + image: quay.io/metallb/frr-k8s:main + name: cp-reloader + volumeMounts: + - mountPath: /etc/frr_reloader + name: reloader + - command: + - /bin/sh + - -c + - cp -f /frr-metrics /etc/frr_metrics/ + image: quay.io/metallb/frr-k8s:main + name: cp-metrics + volumeMounts: + - mountPath: /etc/frr_metrics + name: metrics + - command: + - /bin/sh + - -c + - cp -f /frr-status /etc/frr_status/ + image: quay.io/metallb/frr-k8s:main + name: cp-frr-status + volumeMounts: + - mountPath: /etc/frr_status + name: frr-status + serviceAccountName: frr-k8s-daemon + shareProcessNamespace: true + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + volumes: + - emptyDir: {} + name: frr-sockets + - configMap: + name: frr-k8s-frr-startup + name: frr-startup + - emptyDir: {} + name: frr-conf + - emptyDir: {} + name: reloader + - emptyDir: {} + name: metrics + - emptyDir: {} + name: frr-status + - emptyDir: {} + name: frr-lib + - emptyDir: {} + name: frr-tmp +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: frr-k8s-validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: frr-k8s-webhook-service + namespace: frr-k8s-system + path: /validate-frrk8s-metallb-io-v1beta1-frrconfiguration + failurePolicy: Fail + name: frrconfigurationsvalidationwebhook.metallb.io + rules: + - apiGroups: + - frrk8s.metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - frrconfigurations + sideEffects: None diff --git a/assets/optional/udn/frr-k8s/kustomization.yaml b/assets/optional/udn/frr-k8s/kustomization.yaml new file mode 100644 index 0000000000..064c2dc076 --- /dev/null +++ b/assets/optional/udn/frr-k8s/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - frr-k8s.yaml diff --git a/assets/release/release-x86_64.json b/assets/release/release-x86_64.json index ca725220fe..9e1b2f1ca2 100644 --- a/assets/release/release-x86_64.json +++ b/assets/release/release-x86_64.json @@ -7,7 +7,7 @@ "coredns": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:026ff14d8631d93245a49f924c9459e5f492cf910ac88b8caa351b4d7f6eccf4", "haproxy-router": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:f27be57b9598a2c23ecf20f8ee7aeb9881fadd48b2da50dcf58156f8eac4ed98", "kube-rbac-proxy": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:5edf1128314829b0dc61dd7035da714b303bf3eddb8f3206ac2ac6646335e3d5", - "ovn-kubernetes-microshift": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:02a5485f93b3c117a165caf5057886cc30bf11ba4fa2a8250c21b4694ae4c9ab", + "ovn-kubernetes-microshift": "quay.io/pmatusza/ovn-daemonset-fedora@sha256:3cfcbafd351630864a7b1ff24d3a8df559e454eab6ae19b13c9a74e27ec2c05f", "pod": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:bac51a0992ecf2fa6198771fe05d56868c74a93a1ab556bde4953b38a5789393", "service-ca-operator": "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:73d263abbca11637f3d3ecbb26d9d8ad078eb50a9b6dc23472ea4b1037b652e8", "lvms_operator": "registry.redhat.io/lvms4/lvms-rhel9-operator@sha256:58804d8baf922927b66cec9424d431a3bdb341d207024ce40cc8f0123bac03ee", diff --git a/packaging/systemd/configure-ovs.sh b/packaging/systemd/configure-ovs.sh index e8010dbd23..e7ea72c179 100755 --- a/packaging/systemd/configure-ovs.sh +++ b/packaging/systemd/configure-ovs.sh @@ -9,6 +9,43 @@ NM_CONN_PATH="/etc/NetworkManager/system-connections" # this flag tracks if any config change was made nm_config_changed=0 MANAGED_NM_CONN_SUFFIX="-slave-ovs-clone" + +# when creating the bridge, we use a value lower than NM's ethernet device default route metric +# (we pick 48 and 49 to be lower than anything that NM chooses by default) +BRIDGE_METRIC="48" +BRIDGE1_METRIC="49" + +# Configuration paths +MICROSHIFT_CONFIG_FILE_PATH="/etc/microshift/config.yaml" +ovnk_config_dir='/etc/ovnk' +ovnk_var_dir='/var/lib/ovnk' +extra_bridge_file="${ovnk_config_dir}/extra_bridge" +iface_default_hint_file="${ovnk_var_dir}/iface_default_hint" + +# Parse gatewayInterface from MicroShift config file +# Returns the value of network.gatewayInterface or empty string +get_gateway_interface_config() { + if [ ! -f "$MICROSHIFT_CONFIG_FILE_PATH" ]; then + echo "" + return + fi + # Parse YAML to get network.gatewayInterface value + # This handles both "gatewayInterface: value" and "gatewayInterface: 'value'" formats + local value="" + value=$(awk ' + /^network:/ { in_network=1; next } + in_network && /^[a-zA-Z]/ && !/^ / { in_network=0 } + in_network && /gatewayInterface:/ { + gsub(/.*gatewayInterface:[[:space:]]*/, "") + gsub(/["\047]/, "") # Remove quotes + gsub(/[[:space:]]*$/, "") # Trim trailing whitespace + print + exit + } + ' "$MICROSHIFT_CONFIG_FILE_PATH" 2>/dev/null || echo "") + echo "$value" +} + # Workaround to ensure OVS is installed due to bug in systemd Requires: # https://bugzilla.redhat.com/show_bug.cgi?id=1888017 copy_nm_conn_files() { @@ -31,6 +68,7 @@ copy_nm_conn_files() { fi done } + update_nm_conn_files() { bridge_name=${1} port_name=${2} @@ -44,6 +82,7 @@ update_nm_conn_files() { MANAGED_NM_CONN_FILES+=(${NM_CONN_PATH}/*${MANAGED_NM_CONN_SUFFIX}.nmconnection ${NM_CONN_PATH}/*${MANAGED_NM_CONN_SUFFIX}) shopt -u nullglob } + # Used to remove files managed by configure-ovs rm_nm_conn_files() { for file in "${MANAGED_NM_CONN_FILES[@]}"; do @@ -54,6 +93,7 @@ rm_nm_conn_files() { fi done } + # Used to remove a bridge remove_ovn_bridges() { bridge_name=${1} @@ -67,17 +107,19 @@ remove_ovn_bridges() { # so remove explicitly ovs-vsctl --timeout=30 --if-exists del-br ${bridge_name} } + # Removes any previous ovs configuration remove_all_ovn_bridges() { echo "Reverting any previous OVS configuration" - + remove_ovn_bridges br-ex phys0 if [ -d "/sys/class/net/br-ex1" ]; then remove_ovn_bridges br-ex1 phys1 fi - + echo "OVS configuration successfully reverted" } + # Reloads NM NetworkManager profiles if any configuration change was done. # Accepts a list of devices that should be re-connect after reload. reload_profiles_nm() { @@ -99,10 +141,10 @@ reload_profiles_nm() { # Only attempt to connect a disconnected device local connected_state=$(nmcli -g GENERAL.STATE device show "$dev" || echo "") if [[ "$connected_state" =~ "disconnected" ]]; then - # keep track if a profile by the same name as the device existed + # keep track if a profile by the same name as the device existed # before we attempt activation local named_profile_existed=$([ -f "${NM_CONN_PATH}/${dev}" ] || [ -f "${NM_CONN_PATH}/${dev}.nmconnection" ] && echo "yes") - + for i in {1..10}; do echo "Attempt $i to connect device $dev" nmcli device connect "$dev" && break @@ -126,17 +168,19 @@ reload_profiles_nm() { done nm_config_changed=0 } + # Removes all configuration and reloads NM if necessary rollback_nm() { phys0=$(get_bridge_physical_interface ovs-if-phys0) phys1=$(get_bridge_physical_interface ovs-if-phys1) - + # Revert changes made by /usr/local/bin/configure-ovs.sh during SDN migration. remove_all_ovn_bridges - + # reload profiles so that NM notices that some were removed reload_profiles_nm "$phys0" "$phys1" } + # Accepts parameters $bridge_interface (e.g. ovs-port-phys0) # Returns the physical interface name if $bridge_interface exists, "" otherwise get_bridge_physical_interface() { @@ -146,6 +190,438 @@ get_bridge_physical_interface() { echo "${physical_interface}" } +############################################################################### +# Uplink mode functions - used when network.gatewayInterface is set +############################################################################### + +# Used to clone a slave connection by uuid, returns new uuid +clone_slave_connection() { + local uuid="$1" + local old_name + old_name="$(nmcli -g connection.id connection show uuid "$uuid")" + local new_name="${old_name}${MANAGED_NM_CONN_SUFFIX}" + if nmcli connection show id "${new_name}" &> /dev/null; then + echo "WARN: existing ovs slave ${new_name} connection profile file found, overwriting..." >&2 + nmcli connection delete id "${new_name}" &> /dev/null + fi + nmcli connection clone $uuid "${new_name}" &> /dev/null + nmcli -g connection.uuid connection show "${new_name}" +} + +# Used to replace an old master connection uuid with a new one on all connections +replace_connection_master() { + local old="$1" + local new="$2" + for conn_uuid in $(nmcli -g UUID connection show) ; do + if [ "$(nmcli -g connection.master connection show uuid "$conn_uuid")" != "$old" ]; then + continue + fi + local active_state=$(nmcli -g GENERAL.STATE connection show "$conn_uuid") + local autoconnect=$(nmcli -g connection.autoconnect connection show "$conn_uuid") + if [ "$active_state" != "activated" ] && [ "$autoconnect" != "yes" ]; then + # Assume that slave profiles intended to be used are those that are: + # - active + # - or inactive (which might be due to link being down) but to be autoconnected. + # Otherwise, ignore them. + continue + fi + # make changes for slave profiles in a new clone + local new_uuid + new_uuid=$(clone_slave_connection $conn_uuid) + nmcli conn mod uuid $new_uuid connection.master "$new" + nmcli conn mod $new_uuid connection.autoconnect-priority 100 + nmcli conn mod $new_uuid connection.autoconnect no + echo "Replaced master $old with $new for slave profile $new_uuid" + done +} + +# Add a deactivated connection profile +add_nm_conn() { + nmcli c add "$@" connection.autoconnect no +} + +# Activates an ordered set of NM connection profiles +activate_nm_connections() { + local connections=("$@") + + # make sure to set bond or team slaves autoconnect, otherwise as we + # activate one slave, the other slave might get implicitly re-activated + # with the old profile, activating the old master, interfering and + # causing the former activation to fail. + for conn in "${connections[@]}"; do + local slave_type=$(nmcli -g connection.slave-type connection show "$conn") + if [ "$slave_type" = "team" ] || [ "$slave_type" = "bond" ]; then + nmcli c mod "$conn" connection.autoconnect yes + fi + done + # Then activate all the connections + for conn in "${connections[@]}"; do + local active_state=$(nmcli -g GENERAL.STATE conn show "$conn") + if [ "$active_state" != "activated" ]; then + for i in {1..10}; do + echo "Attempt $i to bring up connection $conn" + nmcli conn up "$conn" && s=0 && break || s=$? + sleep 5 + done + if [ $s -eq 0 ]; then + echo "Brought up connection $conn successfully" + else + echo "ERROR: Cannot bring up connection $conn after $i attempts" + return $s + fi + else + echo "Connection $conn already activated" + fi + nmcli c mod "$conn" connection.autoconnect yes + done +} + +# Writes content of $iface into $iface_default_hint_file +write_iface_default_hint() { + local iface_default_hint_file="$1" + local iface="$2" + echo "${iface}" >| "${iface_default_hint_file}" +} + +# Returns the stored interface default hint if valid +get_iface_default_hint() { + local iface_default_hint_file=$1 + if [ -f "${iface_default_hint_file}" ]; then + local iface_default_hint=$(cat "${iface_default_hint_file}") + if [ "${iface_default_hint}" != "" ] && + [ "${iface_default_hint}" != "br-ex" ] && + [ "${iface_default_hint}" != "br-ex1" ] && + [ -d "/sys/class/net/${iface_default_hint}" ]; then + echo "${iface_default_hint}" + return + fi + fi + echo "" +} + +# Finds the default interface for uplink mode +get_default_interface() { + local iface="" + local counter=0 + local iface_default_hint_file="$1" + local extra_bridge_file="$2" + local extra_bridge="" + if [ -f "${extra_bridge_file}" ]; then + extra_bridge=$(cat ${extra_bridge_file}) + fi + # find default interface + while [ ${counter} -lt 12 ]; do + # check ipv4 + if [ "${extra_bridge}" != "" ]; then + iface=$(ip route show default | grep -v "br-ex1" | grep -v "${extra_bridge}" | awk '{ if ($4 == "dev") { print $5; exit } }') + else + iface=$(ip route show default | grep -v "br-ex1" | awk '{ if ($4 == "dev") { print $5; exit } }') + fi + if [[ -n "${iface}" ]]; then + break + fi + # check ipv6 + if [ "${extra_bridge}" != "" ]; then + iface=$(ip -6 route show default | grep -v "br-ex1" | grep -v "${extra_bridge}" | awk '{ if ($4 == "dev") { print $5; exit } }') + else + iface=$(ip -6 route show default | grep -v "br-ex1" | awk '{ if ($4 == "dev") { print $5; exit } }') + fi + if [[ -n "${iface}" ]]; then + break + fi + + # Interface may not get default route immediately after boot + if [ ${counter} -gt 8 ]; then + if [ "${extra_bridge}" != "" ]; then + iface=$(ip route show | grep -v "br-ex1" | grep -v "${extra_bridge}" | awk '{ if ($2 == "dev") { print $3; exit } }') + else + iface=$(ip route show | grep -v "br-ex1" | awk '{ if ($2 == "dev") { print $3; exit } }') + fi + if [[ -n "${iface}" ]]; then + break + fi + if [ "${extra_bridge}" != "" ]; then + iface=$(ip -6 route show | grep -v "br-ex1" | grep -v "${extra_bridge}" | awk '{ if ($2 == "dev") { print $3; exit } }') + else + iface=$(ip -6 route show | grep -v "br-ex1" | grep -w -v "lo" | awk '{ if ($2 == "dev") { print $3; exit } }') + fi + if [[ -n "${iface}" ]]; then + break + fi + fi + + counter=$((counter+1)) + sleep 5 + done + # if the default interface does not point out of br-ex or br-ex1 + if [ "${iface}" != "br-ex" ] && [ "${iface}" != "br-ex1" ]; then + iface_default_hint=$(get_iface_default_hint "${iface_default_hint_file}") + if [ "${iface_default_hint}" != "" ] && + [ "${iface_default_hint}" != "${iface}" ]; then + while [ ${counter} -le 12 ]; do + if [ "$(ip route show default dev "${iface_default_hint}")" != "" ]; then + iface="${iface_default_hint}" + break + fi + if [ "$(ip -6 route show default dev "${iface_default_hint}")" != "" ]; then + iface="${iface_default_hint}" + break + fi + if [ "$(ip route show dev "${iface_default_hint}")" != "" ]; then + iface="${iface_default_hint}" + break + fi + if [ "$(ip -6 route show dev "${iface_default_hint}")" != "" ]; then + iface="${iface_default_hint}" + break + fi + counter=$((counter+1)) + sleep 5 + done + fi + if [ "${iface}" != "" ]; then + write_iface_default_hint "${iface_default_hint_file}" "${iface}" + fi + fi + echo "${iface}" +} + +# Configure driver options for vmxnet3 +configure_driver_options() { + intf=$1 + if [ ! -f "/sys/class/net/${intf}/device/uevent" ]; then + echo "Device file doesn't exist, skipping setting multicast mode" + else + driver=$(cat "/sys/class/net/${intf}/device/uevent" | grep DRIVER | awk -F "=" '{print $2}') + echo "Driver name is" $driver + if [ "$driver" = "vmxnet3" ]; then + ifconfig "$intf" allmulti + fi + fi +} + +# Given an interface, generates NM configuration to add to an OVS bridge +convert_to_bridge() { + local iface=${1} + local bridge_name=${2} + local port_name=${3} + local bridge_metric=${4} + local ovs_port="ovs-port-${bridge_name}" + local ovs_interface="ovs-if-${bridge_name}" + local default_port_name="ovs-port-${port_name}" # ovs-port-phys0 + local bridge_interface_name="ovs-if-${port_name}" # ovs-if-phys0 + if [ "$iface" = "$bridge_name" ]; then + ifaces=$(ovs-vsctl list-ifaces ${iface}) + for intf in $ifaces; do configure_driver_options $intf; done + echo "Networking already configured and up for ${bridge_name}!" + return + fi + nm_config_changed=1 + if [ -z "$iface" ]; then + echo "ERROR: Unable to find default gateway interface" + exit 1 + fi + # find the MAC from the default interface + if ! iface_mac=$(<"/sys/class/net/${iface}/address"); then + echo "Unable to determine default interface MAC" + exit 1 + fi + echo "MAC address found for iface: ${iface}: ${iface_mac}" + # find MTU from original iface + iface_mtu=$(ip link show "$iface" | awk '{print $5; exit}') + if [[ -z "$iface_mtu" ]]; then + echo "Unable to determine default interface MTU, defaulting to 1500" + iface_mtu=1500 + else + echo "MTU found for iface: ${iface}: ${iface_mtu}" + fi + # store old conn for later + old_conn=$(nmcli --fields UUID,DEVICE conn show --active | awk "/\s${iface}\s*\$/ {print \$1}") + # create bridge + if ! nmcli connection show "$bridge_name" &> /dev/null; then + ovs-vsctl --timeout=30 --if-exists del-br "$bridge_name" + add_nm_conn type ovs-bridge con-name "$bridge_name" conn.interface "$bridge_name" 802-3-ethernet.mtu ${iface_mtu} + fi + # find default port to add to bridge + if ! nmcli connection show "$default_port_name" &> /dev/null; then + ovs-vsctl --timeout=30 --if-exists del-port "$bridge_name" ${iface} + add_nm_conn type ovs-port conn.interface ${iface} master "$bridge_name" con-name "$default_port_name" + fi + if ! nmcli connection show "$ovs_port" &> /dev/null; then + ovs-vsctl --timeout=30 --if-exists del-port "$bridge_name" "$bridge_name" + add_nm_conn type ovs-port conn.interface "$bridge_name" master "$bridge_name" con-name "$ovs_port" + fi + extra_phys_args=() + # check if this interface is a vlan, bond, team, or ethernet type + if [ $(nmcli --get-values connection.type conn show ${old_conn}) == "vlan" ]; then + iface_type=vlan + vlan_id=$(nmcli --get-values vlan.id conn show ${old_conn}) + if [ -z "$vlan_id" ]; then + echo "ERROR: unable to determine vlan_id for vlan connection: ${old_conn}" + exit 1 + fi + vlan_parent=$(nmcli --get-values vlan.parent conn show ${old_conn}) + if [ -z "$vlan_parent" ]; then + echo "ERROR: unable to determine vlan_parent for vlan connection: ${old_conn}" + exit 1 + fi + extra_phys_args=( dev "${vlan_parent}" id "${vlan_id}" ) + elif [ $(nmcli --get-values connection.type conn show ${old_conn}) == "bond" ]; then + iface_type=bond + bond_opts=$(nmcli --get-values bond.options conn show ${old_conn}) + if [ -n "$bond_opts" ]; then + extra_phys_args+=( bond.options "${bond_opts}" ) + MODE_REGEX="(^|,)mode=active-backup(,|$)" + MAC_REGEX="(^|,)fail_over_mac=(1|active|2|follow)(,|$)" + if [[ $bond_opts =~ $MODE_REGEX ]] && [[ $bond_opts =~ $MAC_REGEX ]]; then + clone_mac=0 + fi + fi + elif [ $(nmcli --get-values connection.type conn show ${old_conn}) == "team" ]; then + iface_type=team + team_config_opts=$(nmcli --get-values team.config -e no conn show ${old_conn}) + if [ -n "$team_config_opts" ]; then + extra_phys_args+=( team.config "${team_config_opts//[[:space:]]/}" ) + team_mode=$(echo "${team_config_opts}" | jq -r ".runner.name // empty") + team_mac_policy=$(echo "${team_config_opts}" | jq -r ".runner.hwaddr_policy // empty") + MAC_REGEX="(by_active|only_active)" + if [ "$team_mode" = "activebackup" ] && [[ "$team_mac_policy" =~ $MAC_REGEX ]]; then + clone_mac=0 + fi + fi + elif [ $(nmcli --get-values connection.type conn show ${old_conn}) == "tun" ]; then + iface_type=tun + tun_mode=$(nmcli --get-values tun.mode -e no connection show ${old_conn}) + extra_phys_args+=( tun.mode "${tun_mode}" ) + else + iface_type=802-3-ethernet + fi + if [ ! "${clone_mac:-}" = "0" ]; then + extra_phys_args+=( 802-3-ethernet.cloned-mac-address "${iface_mac}" ) + fi + if ! nmcli connection show "$bridge_interface_name" &> /dev/null; then + ovs-vsctl --timeout=30 --if-exists destroy interface ${iface} + add_nm_conn type ${iface_type} conn.interface ${iface} master "$default_port_name" con-name "$bridge_interface_name" \ + connection.autoconnect-priority 100 802-3-ethernet.mtu ${iface_mtu} ${extra_phys_args[@]+"${extra_phys_args[@]}"} + fi + # Get the new connection uuids + new_conn=$(nmcli -g connection.uuid conn show "$bridge_interface_name") + ovs_port_conn=$(nmcli -g connection.uuid conn show "$ovs_port") + # Update connections with master property set to use the new connection + replace_connection_master $old_conn $new_conn + replace_connection_master $iface $new_conn + if ! nmcli connection show "$ovs_interface" &> /dev/null; then + ovs-vsctl --timeout=30 --if-exists destroy interface "$bridge_name" + if nmcli --fields ipv4.method,ipv6.method conn show $old_conn | grep manual; then + echo "Static IP addressing detected on default gateway connection: ${old_conn}" + nmcli conn clone "${old_conn}" "${ovs_interface}" + shopt -s nullglob + new_conn_files=(${NM_CONN_PATH}/"${ovs_interface}"*) + shopt -u nullglob + if [ ${#new_conn_files[@]} -ne 1 ] || [ ! -f "${new_conn_files[0]}" ]; then + echo "ERROR: could not find ${ovs_interface} conn file after cloning from ${old_conn}" + exit 1 + fi + new_conn_file="${new_conn_files[0]}" + sed -i '/^multi-connect=.*$/d' ${new_conn_file} + sed -i '/^autoconnect=.*$/d' ${new_conn_file} + sed -i '/^\[connection\]$/a autoconnect=false' ${new_conn_file} + sed -i '/^\[connection\]$/,/^\[/ s/^type=.*$/type=ovs-interface/' ${new_conn_file} + sed -i '/^\[connection\]$/a slave-type=ovs-port' ${new_conn_file} + sed -i '/^\[connection\]$/a master='"$ovs_port_conn" ${new_conn_file} + cat <> ${new_conn_file} +[ovs-interface] +type=internal +EOF + nmcli c load ${new_conn_file} + nmcli c mod "${ovs_interface}" conn.interface "$bridge_name" \ + 802-3-ethernet.mtu ${iface_mtu} 802-3-ethernet.cloned-mac-address ${iface_mac} \ + ipv4.route-metric "${bridge_metric}" ipv6.route-metric "${bridge_metric}" + echo "Loaded new $ovs_interface connection file: ${new_conn_file}" + else + extra_if_brex_args="" + num_ipv4_addrs=$(ip -j a show dev ${iface} | jq ".[0].addr_info | map(. | select(.family == \"inet\")) | length") + if [ "$num_ipv4_addrs" -gt 0 ]; then + extra_if_brex_args+="ipv4.may-fail no " + fi + num_ip6_addrs=$(ip -j a show dev ${iface} | jq ".[0].addr_info | map(. | select(.family == \"inet6\" and .scope != \"link\")) | length") + if [ "$num_ip6_addrs" -gt 0 ]; then + extra_if_brex_args+="ipv6.may-fail no " + fi + dhcp_client_id=$(nmcli --get-values ipv4.dhcp-client-id conn show ${old_conn}) + if [ -n "$dhcp_client_id" ]; then + extra_if_brex_args+="ipv4.dhcp-client-id ${dhcp_client_id} " + fi + dhcp6_client_id=$(nmcli --get-values ipv6.dhcp-duid conn show ${old_conn}) + if [ -n "$dhcp6_client_id" ]; then + extra_if_brex_args+="ipv6.dhcp-duid ${dhcp6_client_id} " + fi + ipv6_addr_gen_mode=$(nmcli --get-values ipv6.addr-gen-mode conn show ${old_conn}) + if [ -n "$ipv6_addr_gen_mode" ]; then + extra_if_brex_args+="ipv6.addr-gen-mode ${ipv6_addr_gen_mode} " + fi + ipv4_ignore_auto_dns=$(nmcli --get-values ipv4.ignore-auto-dns conn show ${old_conn}) + if [ -n "$ipv4_ignore_auto_dns" ]; then + extra_if_brex_args+="ipv4.ignore-auto-dns ${ipv4_ignore_auto_dns} " + fi + ipv6_ignore_auto_dns=$(nmcli --get-values ipv6.ignore-auto-dns conn show ${old_conn}) + if [ -n "$ipv6_ignore_auto_dns" ]; then + extra_if_brex_args+="ipv6.ignore-auto-dns ${ipv6_ignore_auto_dns} " + fi + ipv4_dns=$(nmcli --get-values ipv4.dns conn show ${old_conn}) + if [ -n "$ipv4_dns" ]; then + extra_if_brex_args+="ipv4.dns ${ipv4_dns} " + fi + ipv6_dns=$(nmcli --get-values ipv6.dns conn show ${old_conn}) + if [ -n "$ipv6_dns" ]; then + extra_if_brex_args+="ipv6.dns ${ipv6_dns} " + fi + add_nm_conn type ovs-interface slave-type ovs-port conn.interface "$bridge_name" master "$ovs_port_conn" con-name \ + "$ovs_interface" 802-3-ethernet.mtu ${iface_mtu} 802-3-ethernet.cloned-mac-address ${iface_mac} \ + ipv4.route-metric "${bridge_metric}" ipv6.route-metric "${bridge_metric}" ${extra_if_brex_args} + fi + fi + configure_driver_options "${iface}" + update_nm_conn_files "$bridge_name" "$port_name" +} + +# Used to print network state +print_state() { + echo "Current device, connection, interface and routing state:" + nmcli -g all device | grep -v unmanaged + nmcli -g all connection + ip -d address show + ip route show + ip -6 route show +} + +# Setup an exit trap to rollback on error (used in uplink mode) +handle_exit() { + e=$? + [ $e -eq 0 ] && print_state && exit 0 + echo "ERROR: configure-ovs exited with error: $e" + print_state + dir=$(mktemp -d -t "configure-ovs-$(date +%Y-%m-%d-%H-%M-%S)-XXXXXXXXXX") + update_nm_conn_files br-ex phys0 + copy_nm_conn_files "$dir" + update_nm_conn_files br-ex1 phys1 + copy_nm_conn_files "$dir" + echo "Copied OVS configuration to $dir for troubleshooting" + echo "Attempting to restore previous configuration..." + rollback_nm + print_state + exit $e +} + +############################################################################### +# Main script logic +############################################################################### + +# Read gateway interface configuration +gateway_interface=$(get_gateway_interface_config) +echo "Gateway interface configuration: '${gateway_interface}'" + +# Check for and remove any previous NM configuration (upgrade scenario) nm_need_rollback=false update_nm_conn_files br-ex phys0 if [ -d "/sys/class/net/br-ex1" ]; then @@ -158,11 +634,101 @@ for file in "${MANAGED_NM_CONN_FILES[@]}"; do done if [ "${nm_need_rollback}" = "true" ]; then - # Remove NM config files create by previous release when doing upgrade + # Remove NM config files created by previous release when doing upgrade rollback_nm fi -# use a locally administered addresse as the static MAC of br-ex -static_mac="0a:59:00:00:00:01" -ovs-vsctl --timeout=15 --may-exist add-br br-ex -- br-set-external-id br-ex bridge-id br-ex -- set bridge br-ex fail-mode=standalone other_config:hwaddr="${static_mac}" -nmcli device set br-ex managed no +if [ -z "$gateway_interface" ]; then + ########################################################################### + # No-uplink mode (default): Create br-ex with static MAC and no uplink + ########################################################################### + echo "Using no-uplink mode: creating br-ex with static MAC" + + # use a locally administered address as the static MAC of br-ex + static_mac="0a:59:00:00:00:01" + ovs-vsctl --timeout=15 --may-exist add-br br-ex -- br-set-external-id br-ex bridge-id br-ex -- set bridge br-ex fail-mode=standalone other_config:hwaddr="${static_mac}" + nmcli device set br-ex managed no + +else + ########################################################################### + # Uplink mode: Convert physical interface to OVS bridge + ########################################################################### + echo "Using uplink mode with gateway interface: ${gateway_interface}" + + # Set up exit trap for rollback on error + trap "handle_exit" EXIT + + # Create config directories + mkdir -p "${ovnk_config_dir}" + mkdir -p "${ovnk_var_dir}" + + # Handle gateway interface setting + if [ "$gateway_interface" != "auto" ]; then + # User specified a specific interface + write_iface_default_hint "${iface_default_hint_file}" "${gateway_interface}" + fi + + # For upgrade scenarios, stabilize existing configuration + iface_default_hint=$(get_iface_default_hint "${iface_default_hint_file}") + if [ "${iface_default_hint}" == "" ]; then + current_interface=$(get_bridge_physical_interface ovs-if-phys0) + if [ "${current_interface}" != "" ]; then + write_iface_default_hint "${iface_default_hint_file}" "${current_interface}" + fi + fi + + # Handle conflicting hint files + if [ -f "${iface_default_hint_file}" ] && + [ -f "${extra_bridge_file}" ] && + [ "$(cat "${iface_default_hint_file}")" == "$(cat "${extra_bridge_file}")" ]; then + echo "${iface_default_hint_file} and ${extra_bridge_file} share the same content" + echo "Deleting file ${iface_default_hint_file} to choose a different interface" + rm -f "${iface_default_hint_file}" + rm -f /run/configure-ovs-boot-done + fi + + # On every boot, rollback and regenerate configuration + if [ ! -f /run/configure-ovs-boot-done ]; then + echo "Running on boot, restoring previous configuration before proceeding..." + rollback_nm + print_state + fi + touch /run/configure-ovs-boot-done + + # Determine the interface to use + iface=$(get_default_interface "${iface_default_hint_file}" "$extra_bridge_file") + + if [ "$iface" != "br-ex" ]; then + if [ -f "$extra_bridge_file" ] || [ -z "$(nmcli connection show --active br-ex 2> /dev/null)" ]; then + echo "Bridge br-ex is not active, restoring previous configuration before proceeding..." + rollback_nm + print_state + fi + fi + + # Convert the interface to an OVS bridge + convert_to_bridge "$iface" "br-ex" "phys0" "${BRIDGE_METRIC}" + + # Remove second bridge if it exists + if nmcli connection show br-ex1 &> /dev/null || nmcli connection show ovs-if-phys1 &> /dev/null; then + update_nm_conn_files br-ex1 phys1 + rm_nm_conn_files + fi + + if [ -f "$extra_bridge_file" ]; then + rm -f "$extra_bridge_file" + fi + + # Remove bridges created by openshift-sdn + ovs-vsctl --timeout=30 --if-exists del-br br0 + + # Make sure everything is activated + connections=() + while IFS= read -r connection; do + if [[ $connection == *"$MANAGED_NM_CONN_SUFFIX" ]]; then + connections+=("$connection") + fi + done < <(nmcli -g NAME c) + connections+=(ovs-if-phys0 ovs-if-br-ex) + activate_nm_connections "${connections[@]}" +fi diff --git a/pkg/components/networking.go b/pkg/components/networking.go index 0e1e2fa76d..2f32d2b933 100644 --- a/pkg/components/networking.go +++ b/pkg/components/networking.go @@ -35,17 +35,26 @@ func startCNIPlugin(ctx context.Context, cfg *config.Config, kubeconfigPath stri "components/ovn/common/master-serviceaccount.yaml", "components/ovn/common/node-serviceaccount.yaml", } + crd = []string{ + "components/ovn/common/crd-udn.yaml", + "components/ovn/common/crd-cudn.yaml", + "components/ovn/common/crd-ra.yaml", + } r = []string{ "components/ovn/common/role.yaml", + "components/ovn/common/role-sbdb.yaml", } rb = []string{ "components/ovn/common/rolebinding.yaml", + "components/ovn/common/rolebinding-sbdb.yaml", } cr = []string{ "components/ovn/common/clusterrole.yaml", + "components/ovn/common/clusterrole-controller.yaml", } crb = []string{ "components/ovn/common/clusterrolebinding.yaml", + "components/ovn/common/clusterrolebinding-controller.yaml", } cm = []string{ "components/ovn/common/configmap.yaml", @@ -85,6 +94,10 @@ func startCNIPlugin(ctx context.Context, cfg *config.Config, kubeconfigPath stri klog.Warningf("Failed to apply ns %v: %v", ns, err) return err } + if err := assets.ApplyCRDAndWaitForEstablish(ctx, crd, kubeconfigPath); err != nil { + klog.Warningf("Failed to apply crd %v: %v", crd, err) + return err + } if err := assets.ApplyServiceAccounts(ctx, sa, kubeconfigPath); err != nil { klog.Warningf("Failed to apply serviceAccount %v %v", sa, err) return err diff --git a/pkg/config/config.go b/pkg/config/config.go index 515096f001..3e76181008 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -230,6 +230,10 @@ func (c *Config) incorporateUserSettings(u *Config) { c.Network.Multus.Status = u.Network.Multus.Status } + if u.Network.GatewayInterface != "" { + c.Network.GatewayInterface = u.Network.GatewayInterface + } + if u.Etcd.MemoryLimitMB != 0 { c.Etcd.MemoryLimitMB = u.Etcd.MemoryLimitMB } @@ -674,6 +678,9 @@ func (c *Config) validate() error { if err := c.Network.Multus.Validate(); err != nil { return fmt.Errorf("error validating multus configuration: %v", err) } + if err := c.Network.ValidateGatewayInterface(); err != nil { + return fmt.Errorf("error validating network.gatewayInterface: %v", err) + } return nil } diff --git a/pkg/config/network.go b/pkg/config/network.go index 192256ba7e..a0e8582b87 100644 --- a/pkg/config/network.go +++ b/pkg/config/network.go @@ -76,6 +76,13 @@ type Network struct { // +kubebuilder:default="30000-32767" ServiceNodePortRange string `json:"serviceNodePortRange"` + // GatewayInterface specifies the physical interface to use as the OVS gateway bridge (br-ex) uplink. + // When set, the interface's IP configuration is transferred to br-ex, enabling external network connectivity. + // When empty (default), br-ex is created with a static MAC address and no physical uplink. + // Use "auto" to auto-detect the default gateway interface. + // +kubebuilder:validation:Optional + GatewayInterface string `json:"gatewayInterface,omitempty"` + Multus Multus `json:"multus"` // The DNS server to use @@ -134,3 +141,18 @@ func (m Multus) Validate() error { } return nil } + +// ValidateGatewayInterface checks that the gateway interface configuration is valid. +// It returns nil if the interface is empty (no-uplink mode), "auto" (auto-detect), or +// if the specified interface exists on the system. +func (n Network) ValidateGatewayInterface() error { + if n.GatewayInterface == "" || n.GatewayInterface == "auto" { + return nil + } + + _, err := net.InterfaceByName(n.GatewayInterface) + if err != nil { + return fmt.Errorf("gateway interface %q not found: %w", n.GatewayInterface, err) + } + return nil +} diff --git a/pkg/config/network_test.go b/pkg/config/network_test.go index 44d489bbef..9824750aed 100644 --- a/pkg/config/network_test.go +++ b/pkg/config/network_test.go @@ -79,3 +79,45 @@ func TestNetwork_cniPluginIsValid(t *testing.T) { }) } } + +func TestNetwork_ValidateGatewayInterface(t *testing.T) { + tests := []struct { + name string + gatewayInterface string + wantErr bool + }{ + { + name: "empty value is valid (no-uplink mode)", + gatewayInterface: "", + wantErr: false, + }, + { + name: "auto is valid", + gatewayInterface: "auto", + wantErr: false, + }, + { + name: "lo interface exists and is valid", + gatewayInterface: "lo", + wantErr: false, + }, + { + name: "non-existent interface is invalid", + gatewayInterface: "nonexistent-iface-12345", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := Network{ + GatewayInterface: tt.gatewayInterface, + } + err := n.ValidateGatewayInterface() + if tt.wantErr { + assert.Error(t, err, "ValidateGatewayInterface() should return error") + } else { + assert.NoError(t, err, "ValidateGatewayInterface() should not return error") + } + }) + } +} diff --git a/pkg/config/ovn/ovn.go b/pkg/config/ovn/ovn.go index c6bd17aab1..536dabd700 100644 --- a/pkg/config/ovn/ovn.go +++ b/pkg/config/ovn/ovn.go @@ -18,7 +18,7 @@ const ( ovnConfigFileName = "ovn.yaml" OVNGatewayInterface = "br-ex" defaultMTU = 1500 - OVNKubernetesV4MasqueradeIP = "169.254.169.2" + OVNKubernetesV4MasqueradeIP = "169.254.0.2" OVNKubernetesV6MasqueradeIP = "fd69::2" // used for multinode ovn database transport diff --git a/pkg/util/cert.go b/pkg/util/cert.go index a64bfe5c4e..a3c7ccf9fb 100644 --- a/pkg/util/cert.go +++ b/pkg/util/cert.go @@ -181,7 +181,7 @@ func VerifyAllowedSNI(advertiseAddress string, clusterNetwork []string, serviceN } else { if ContainIPANetwork(ipAddress, clusterNetwork) || ContainIPANetwork(ipAddress, serviceNetwork) || - ContainIPANetwork(ipAddress, []string{"127.0.0.1/8", "169.254.169.2/29", "::1/128", "fe80::/10", "fd69::/125"}) || + ContainIPANetwork(ipAddress, []string{"127.0.0.1/8", "169.254.0.0/17", "::1/128", "fe80::/10", "fd69::/112"}) || advertiseAddress == ipAddress.String() { return false }