diff --git a/ci-operator/config/openshift/cluster-network-operator/openshift-cluster-network-operator-release-5.0.yaml b/ci-operator/config/openshift/cluster-network-operator/openshift-cluster-network-operator-release-5.0.yaml index eaebb53d77451..f521bfc9392f5 100644 --- a/ci-operator/config/openshift/cluster-network-operator/openshift-cluster-network-operator-release-5.0.yaml +++ b/ci-operator/config/openshift/cluster-network-operator/openshift-cluster-network-operator-release-5.0.yaml @@ -358,10 +358,60 @@ tests: workflow: openshift-e2e-aws-ovn-local-to-shared-gateway-mode-migration - always_run: false as: e2e-aws-hypershift-ovn-kubevirt + capabilities: + - intranet + cluster: build09 optional: true steps: - cluster_profile: openshift-org-aws - workflow: hypershift-kubevirt-conformance + cluster_profile: equinix-ocp-hcp + env: + ATTACH_DEFAULT_NETWORK: localnet + CNV_SUBSCRIPTION_SOURCE: redhat-operators-v4-20 + HYPERSHIFT_NODE_COUNT: "3" + HYPERSHIFT_NODE_MEMORY: "16" + KUBEVIRT_CSI_INFRA: lvms-vg1 + LOCALNET_ATTACH_DEFAULT: "false" + LOCALNET_SUBNET: 192.168.111.0/24 + pre: + - chain: baremetalds-ofcir-pre + - ref: hypershift-kubevirt-install + - chain: hypershift-kubevirt-baremetalds-lvm + - chain: hypershift-kubevirt-baremetalds-metallb + - ref: hypershift-install + - ref: hypershift-agent-create-config-dns + - ref: hypershift-kubevirt-create + - ref: hypershift-kubevirt-baremetalds-proxy + - ref: hypershift-kubevirt-health-check + - ref: cucushift-installer-reportportal-marker + test: + - as: wait + cli: latest + commands: | + echo "Cluster is ready — localnet-as-primary debug session" + echo "Management cluster KUBECONFIG: \$KUBECONFIG" + echo "Hosted cluster KUBECONFIG: \${SHARED_DIR}/nested_kubeconfig" + echo "" + echo "Localnet-as-primary config:" + echo " --attach-default-network=false" + echo " Localnet subnet: 192.168.111.0/24" + echo " VMs have ONLY localnet interface (eth0)" + echo "" + oc get nodes + echo "" + echo "Hosted cluster nodes:" + KUBECONFIG="${SHARED_DIR}/nested_kubeconfig" oc get nodes 2>/dev/null || echo "(not ready yet)" + echo "" + echo "Sleeping for 18 hours..." + sleep 64800 + echo "Wait complete." + from: stable:cli + resources: + requests: + cpu: 100m + memory: 200Mi + timeout: 18h30m0s + workflow: cucushift-installer-rehearse-baremetalds-ipi-ovn-dualstack-kubevirt-hypershift + timeout: 20h0m0s - always_run: false as: qe-perfscale-aws-ovn-medium-cluster-density optional: true diff --git a/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-commands.sh b/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-commands.sh index f678c14b9b5fb..87dfbc6ad0b98 100644 --- a/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-commands.sh +++ b/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-commands.sh @@ -136,7 +136,28 @@ fi oc create namespace "${CLUSTER_NAMESPACE_PREFIX}" --dry-run=client -o yaml | oc apply -f - oc create ns "${CLUSTER_NAMESPACE_PREFIX}-${CLUSTER_NAME}" if [[ -n "${ATTACH_DEFAULT_NETWORK}" ]]; then - oc apply -f - <"${SHARED_DIR}/nested_kubeconfig" +# Post-creation localnet setup: DHCP workaround, port security clearing, ipecho deployment +if [[ "${ATTACH_DEFAULT_NETWORK}" == "localnet" ]]; then + LOCALNET_SUBNET="${LOCALNET_SUBNET:-192.168.111.0/24}" + # Derive gateway IP (.1) from the subnet + LOCALNET_GW=$(echo "${LOCALNET_SUBNET}" | sed 's|\.[0-9]*/|.1|') + + echo "Waiting for all VMIs to be Running..." + for i in $(seq 1 60); do + VMI_RUNNING_COUNT=$(oc get vmi -n "${CLUSTER_NAMESPACE_PREFIX}-${CLUSTER_NAME}" --no-headers 2>/dev/null | grep -c Running || true) + if [[ "${VMI_RUNNING_COUNT}" -ge "${HYPERSHIFT_NODE_COUNT}" ]]; then + echo "All ${VMI_RUNNING_COUNT} VMIs are Running" + break + fi + echo "Waiting for VMIs... (${VMI_RUNNING_COUNT}/${HYPERSHIFT_NODE_COUNT} running) [${i}/60]" + sleep 10 + done + + echo "Configuring OVN DHCP options and clearing port security for localnet LSPs..." + for VMI in $(oc get vmi -n "${CLUSTER_NAMESPACE_PREFIX}-${CLUSTER_NAME}" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do + NODE=$(oc get vmi "${VMI}" -n "${CLUSTER_NAMESPACE_PREFIX}-${CLUSTER_NAME}" -o jsonpath='{.status.nodeName}' 2>/dev/null) + if [[ -z "${NODE}" ]]; then + echo "WARNING: Could not find node for VMI ${VMI}, skipping" + continue + fi + + # Find the OVN pod on the node where the VM is scheduled + OVN_POD=$(oc get pods -n openshift-ovn-kubernetes -l app=ovnkube-node \ + --field-selector "spec.nodeName=${NODE}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [[ -z "${OVN_POD}" ]]; then + echo "WARNING: No ovnkube-node pod found on node ${NODE} for VMI ${VMI}, skipping" + continue + fi + + # Find the localnet Logical Switch Port for this VMI + LSP_NAME=$(oc exec -n openshift-ovn-kubernetes "${OVN_POD}" -c nbdb -- \ + ovn-nbctl --columns=name --bare find Logical_Switch_Port \ + "external_ids:k8s.ovn.org/topology=localnet" 2>/dev/null | head -1) + if [[ -z "${LSP_NAME}" ]]; then + echo "WARNING: No localnet LSP found on node ${NODE} for VMI ${VMI}, skipping" + continue + fi + + # Create DHCP options with router, DNS server, and lease time + DHCP_UUID=$(oc exec -n openshift-ovn-kubernetes "${OVN_POD}" -c nbdb -- \ + ovn-nbctl create DHCP_Options cidr="${LOCALNET_SUBNET}" \ + options='"lease_time"="3500" "router"="'"${LOCALNET_GW}"'" "server_id"="'"${LOCALNET_GW}"'" "server_mac"="c0:ff:ee:00:00:01" "dns_server"="'"${LOCALNET_GW}"'"' \ + 2>/dev/null) + + # Bind the DHCP options to the localnet LSP + oc exec -n openshift-ovn-kubernetes "${OVN_POD}" -c nbdb -- \ + ovn-nbctl lsp-set-dhcpv4-options "${LSP_NAME}" "${DHCP_UUID}" 2>/dev/null + + # Clear port security so EgressIP-SNATed packets can exit + oc exec -n openshift-ovn-kubernetes "${OVN_POD}" -c nbdb -- \ + ovn-nbctl clear Logical_Switch_Port "${LSP_NAME}" port_security 2>/dev/null + + echo "Configured DHCP and cleared port security for VMI ${VMI} on node ${NODE} (LSP: ${LSP_NAME})" + done + + # Deploy ip-echo on the management cluster with localnet NAD + IPECHO_NAMESPACE="egressip-ipecho-${CLUSTER_NAME}" + echo "Deploying ip-echo in dedicated namespace ${IPECHO_NAMESPACE}..." + oc create namespace "${IPECHO_NAMESPACE}" --dry-run=client -o yaml | oc apply -f - + oc label ns "${IPECHO_NAMESPACE}" pod-security.kubernetes.io/enforce=privileged --overwrite 2>/dev/null || true + + # Create a localnet NAD in the ip-echo namespace + oc apply -f - < "${SHARED_DIR}/kubevirt_ipecho_url" + echo "Localnet post-creation setup complete" +fi + echo "${CLUSTER_NAME}" > "${SHARED_DIR}/cluster-name" \ No newline at end of file diff --git a/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-ref.yaml b/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-ref.yaml index 6e7517701e910..95670314d0a3d 100644 --- a/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-ref.yaml +++ b/ci-operator/step-registry/hypershift/kubevirt/create/hypershift-kubevirt-create-ref.yaml @@ -36,9 +36,22 @@ ref: - name: ATTACH_DEFAULT_NETWORK default: "" documentation: |- - if true, config additional network for hostedcluster and attach-default-network true; - if false, config additional network for hostedcluster and attach-default-network false - if the default empty string will skip all additional network config. + Controls network attachment for KubeVirt hosted cluster VMs: + - "true": macvlan additional network, attach-default-network=true + - "false": macvlan additional network, attach-default-network=false + - "localnet": OVN localnet network (configurable via LOCALNET_* vars) + - "": skip additional network config (default, pod network only) + - name: LOCALNET_SUBNET + default: "192.168.111.0/24" + documentation: |- + Subnet for the OVN localnet NAD when ATTACH_DEFAULT_NETWORK=localnet. + Use 192.168.111.0/24 for same-L2 bootstrap (VMs can reach mgmt cluster). + - name: LOCALNET_ATTACH_DEFAULT + default: "false" + documentation: |- + Value for --attach-default-network when ATTACH_DEFAULT_NETWORK=localnet. + false = localnet-as-primary (localnet is primary, no pod network). + true = Model 3 (dual-homed: pod network + localnet). - name: ETCD_STORAGE_CLASS default: "" documentation: |-