Skip to content

Commit 523d934

Browse files
committed
e2e: implement e2e suite for hcp using kamaji
Signed-off-by: Bharath Nallapeta <[email protected]>
1 parent b7498dd commit 523d934

File tree

10 files changed

+936
-0
lines changed

10 files changed

+936
-0
lines changed

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,16 @@ test-conformance: $(GINKGO) e2e-prerequisites ## Run clusterctl based conformanc
236236
test-conformance-fast: ## Run clusterctl based conformance test on workload cluster (requires Docker) using a subset of the conformance suite in parallel.
237237
$(MAKE) test-conformance CONFORMANCE_E2E_ARGS="-kubetest.config-file=$(KUBETEST_FAST_CONF_PATH) -kubetest.ginkgo-nodes=5 $(E2E_ARGS)"
238238

239+
# Run HCP tests - Kamaji v1.0.0 is installed via Helm during test setup
240+
HCP_E2E_ARGS ?= $(E2E_ARGS)
241+
.PHONY: test-hcp
242+
test-hcp: $(GINKGO) e2e-prerequisites ## Run hosted control plane (HCP) tests using Kamaji
243+
time $(GINKGO) -fail-fast -trace -timeout=4h -show-node-events -v -tags=e2e \
244+
--output-dir="$(ARTIFACTS)" --junit-report="junit.hcp_suite.1.xml" \
245+
-focus="Hosted Control Plane tests" $(E2E_GINKGO_ARGS) ./test/e2e/suites/hcp/... -- \
246+
-config-path="$(E2E_CONF_PATH)" -artifacts-folder="$(ARTIFACTS)" \
247+
-data-folder="$(E2E_DATA_DIR)" $(HCP_E2E_ARGS)
248+
239249
APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main)
240250

241251
.PHONY: apidiff

docs/book/src/development/development.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,84 @@ kubectl get openstackservers
562562
```
563563

564564
This object is immutable and is created by the controller when a machine or a bastion is created. The `OpenStackServer` object is deleted when the machine or the bastion is deleted.
565+
566+
## Hosted Control Plane (HCP) Testing
567+
568+
CAPO supports testing Hosted Control Planes using Kamaji as the control plane provider. This allows testing scenarios where the Kubernetes control plane runs as pods in a management cluster while worker nodes run on OpenStack infrastructure.
569+
570+
### Prerequisites
571+
572+
* A working OpenStack environment (same requirements as regular e2e tests)
573+
* Helm 3.x installed locally
574+
* Management cluster with sufficient resources for hosting control planes
575+
576+
### Running HCP Tests
577+
578+
To run the hosted control plane e2e tests:
579+
580+
```bash
581+
make test-hcp OPENSTACK_CLOUD_YAML_FILE=/path/to/clouds.yaml OPENSTACK_CLOUD=my_cloud
582+
```
583+
584+
This will:
585+
1. Create a management cluster using kind
586+
2. Install CAPO in the management cluster
587+
3. Install Kamaji v1.0.0 using Helm
588+
4. Install the Kamaji Control Plane Provider v0.15.3
589+
5. Run HCP-specific test suites that validate:
590+
- Network configuration fixes for HCP scenarios
591+
- Security group precedence between machine and cluster levels
592+
- Worker node connectivity to hosted control planes via Konnectivity
593+
- Machine spec validation without explicit networks
594+
595+
### Architecture
596+
597+
The HCP testing follows this architecture:
598+
599+
```
600+
Management Cluster (kind)
601+
├── CAPO Controller
602+
├── Kamaji v1.0.0
603+
├── Kamaji Control Plane Provider v0.15.3
604+
└── TenantControlPlanes (running as pods)
605+
└── Connected to OpenStack worker nodes via Konnectivity
606+
```
607+
608+
### Test Flavors
609+
610+
HCP tests use the `hcp` flavor, which:
611+
- Uses `KamajiControlPlane` instead of `KubeadmControlPlane`
612+
- Configures Konnectivity for worker node communication
613+
- Sets up proper security groups for HCP networking
614+
- Validates the network configuration fixes from the `hcp-2380` branch
615+
616+
### Manual Installation
617+
618+
If you need to install Kamaji manually for development:
619+
620+
```bash
621+
# Install Kamaji using Helm
622+
helm repo add clastix https://clastix.github.io/charts
623+
helm repo update
624+
helm install kamaji clastix/kamaji --version v1.0.0 --namespace kamaji-system --create-namespace --wait
625+
626+
# Or use the convenience script
627+
./hack/install-kamaji.sh
628+
```
629+
630+
### Troubleshooting
631+
632+
**Worker nodes can't connect to control plane:**
633+
- Check Konnectivity service is running
634+
- Verify security groups allow traffic on port 8132
635+
- Ensure LoadBalancer service is properly configured
636+
637+
**TenantControlPlane fails to start:**
638+
- Check Kamaji controller logs: `kubectl logs -n kamaji-system -l app.kubernetes.io/name=kamaji`
639+
- Verify DataStore is ready: `kubectl get datastore -n kamaji-system`
640+
- Check etcd connectivity in the management cluster
641+
642+
**Network validation tests fail:**
643+
- Ensure OpenStack cloud has proper networking configuration
644+
- Verify security groups are properly configured
645+
- Check that external network is accessible for LoadBalancer services

hack/install-kamaji.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/bin/bash
2+
3+
# Copyright 2025 The Kubernetes Authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
set -euo pipefail
18+
19+
# Simple Kamaji installation using Helm
20+
# This script installs Kamaji v1.0.0 in the management cluster
21+
22+
KAMAJI_VERSION="v1.0.0"
23+
KAMAJI_NAMESPACE="kamaji-system"
24+
25+
echo "Installing Kamaji ${KAMAJI_VERSION}..."
26+
27+
# Add Clastix Helm repository
28+
echo "Adding Clastix Helm repository..."
29+
helm repo add clastix https://clastix.github.io/charts
30+
helm repo update
31+
32+
# Install Kamaji
33+
echo "Installing Kamaji in namespace ${KAMAJI_NAMESPACE}..."
34+
helm install kamaji clastix/kamaji \
35+
--version ${KAMAJI_VERSION} \
36+
--namespace ${KAMAJI_NAMESPACE} \
37+
--create-namespace \
38+
--wait
39+
40+
# Verify installation
41+
echo "Verifying Kamaji installation..."
42+
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=kamaji -n ${KAMAJI_NAMESPACE} --timeout=300s
43+
44+
# Create default datastore
45+
echo "Creating default datastore..."
46+
cat <<EOF | kubectl apply -f -
47+
apiVersion: kamaji.clastix.io/v1alpha1
48+
kind: DataStore
49+
metadata:
50+
name: default
51+
namespace: ${KAMAJI_NAMESPACE}
52+
spec:
53+
driver: etcd
54+
endpoints:
55+
- kamaji-etcd.${KAMAJI_NAMESPACE}.svc.cluster.local:2379
56+
EOF
57+
58+
echo "Kamaji installation completed successfully!"
59+
echo "Ready to create TenantControlPlanes with the 'default' datastore."

templates/cluster-template-hcp.yaml

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
apiVersion: v1
2+
data:
3+
cacert: ${OPENSTACK_CLOUD_CACERT_B64}
4+
clouds.yaml: ${OPENSTACK_CLOUD_YAML_B64}
5+
kind: Secret
6+
metadata:
7+
labels:
8+
clusterctl.cluster.x-k8s.io/move: "true"
9+
name: ${CLUSTER_NAME}-cloud-config
10+
---
11+
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
12+
kind: KubeadmConfigTemplate
13+
metadata:
14+
name: ${CLUSTER_NAME}-md-0
15+
spec:
16+
template:
17+
spec:
18+
files: []
19+
joinConfiguration:
20+
nodeRegistration:
21+
kubeletExtraArgs:
22+
cloud-provider: external
23+
provider-id: openstack:///'{{ instance_id }}'
24+
name: '{{ local_hostname }}'
25+
---
26+
apiVersion: cluster.x-k8s.io/v1beta1
27+
kind: Cluster
28+
metadata:
29+
name: ${CLUSTER_NAME}
30+
spec:
31+
clusterNetwork:
32+
pods:
33+
cidrBlocks:
34+
- 192.168.0.0/16
35+
serviceDomain: cluster.local
36+
controlPlaneRef:
37+
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
38+
kind: KamajiControlPlane
39+
name: ${CLUSTER_NAME}-control-plane
40+
infrastructureRef:
41+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
42+
kind: OpenStackCluster
43+
name: ${CLUSTER_NAME}
44+
---
45+
apiVersion: cluster.x-k8s.io/v1beta1
46+
kind: MachineDeployment
47+
metadata:
48+
name: ${CLUSTER_NAME}-md-0
49+
spec:
50+
clusterName: ${CLUSTER_NAME}
51+
replicas: ${WORKER_MACHINE_COUNT}
52+
selector:
53+
matchLabels: null
54+
template:
55+
spec:
56+
bootstrap:
57+
configRef:
58+
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
59+
kind: KubeadmConfigTemplate
60+
name: ${CLUSTER_NAME}-md-0
61+
clusterName: ${CLUSTER_NAME}
62+
failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
63+
infrastructureRef:
64+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
65+
kind: OpenStackMachineTemplate
66+
name: ${CLUSTER_NAME}-md-0
67+
version: ${KUBERNETES_VERSION}
68+
---
69+
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
70+
kind: KamajiControlPlane
71+
metadata:
72+
name: ${CLUSTER_NAME}-control-plane
73+
spec:
74+
dataStoreName: ${CLUSTER_DATASTORE:-default}
75+
addons:
76+
coreDNS: {}
77+
kubeProxy: {}
78+
konnectivity:
79+
enabled: true
80+
proxyMode: HTTPConnect
81+
kubelet:
82+
cgroupfs: systemd
83+
preferredAddressTypes:
84+
- InternalIP
85+
- ExternalIP
86+
network:
87+
serviceType: LoadBalancer
88+
serviceAnnotations:
89+
service.beta.kubernetes.io/openstack-internal-load-balancer: "false"
90+
replicas: ${CONTROL_PLANE_MACHINE_COUNT:-1}
91+
version: ${KUBERNETES_VERSION}
92+
---
93+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
94+
kind: OpenStackCluster
95+
metadata:
96+
name: ${CLUSTER_NAME}
97+
spec:
98+
apiServerLoadBalancer:
99+
enabled: false # Disabled since Kamaji provides its own LoadBalancer service
100+
externalNetwork:
101+
id: ${OPENSTACK_EXTERNAL_NETWORK_ID}
102+
identityRef:
103+
cloudName: ${OPENSTACK_CLOUD}
104+
name: ${CLUSTER_NAME}-cloud-config
105+
managedSecurityGroups:
106+
allNodesSecurityGroupRules:
107+
- description: Created by cluster-api-provider-openstack - BGP (calico)
108+
direction: ingress
109+
etherType: IPv4
110+
name: BGP (Calico)
111+
portRangeMax: 179
112+
portRangeMin: 179
113+
protocol: tcp
114+
remoteManagedGroups:
115+
- worker
116+
- description: Created by cluster-api-provider-openstack - IP-in-IP (calico)
117+
direction: ingress
118+
etherType: IPv4
119+
name: IP-in-IP (calico)
120+
protocol: "4"
121+
remoteManagedGroups:
122+
- worker
123+
# Konnectivity for Kamaji hosted control plane communication
124+
- description: Created by cluster-api-provider-openstack - Konnectivity (Kamaji HCP)
125+
direction: ingress
126+
etherType: IPv4
127+
name: Konnectivity (Kamaji HCP)
128+
portRangeMax: 8132
129+
portRangeMin: 8132
130+
protocol: tcp
131+
remoteManagedGroups:
132+
- worker
133+
managedSubnets:
134+
- cidr: 10.6.0.0/24
135+
dnsNameservers:
136+
- ${OPENSTACK_DNS_NAMESERVERS}
137+
---
138+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
139+
kind: OpenStackMachineTemplate
140+
metadata:
141+
name: ${CLUSTER_NAME}-md-0
142+
spec:
143+
template:
144+
spec:
145+
flavor: ${OPENSTACK_NODE_MACHINE_FLAVOR}
146+
image:
147+
filter:
148+
name: ${OPENSTACK_IMAGE_NAME}
149+
sshKeyName: ${OPENSTACK_SSH_KEY_NAME}

test/e2e/data/e2e_conf.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ providers:
9292
new: "imagePullPolicy: IfNotPresent"
9393
- old: "--leader-elect"
9494
new: "--leader-elect=false\n - --sync-period=1m"
95+
- name: kamaji
96+
type: ControlPlaneProvider
97+
versions:
98+
- name: v0.15.3
99+
value: "https://github.com/clastix/cluster-api-control-plane-provider-kamaji/releases/download/v0.15.3/control-plane-components.yaml"
100+
type: url
101+
contract: v1beta1
102+
files:
103+
- sourcePath: "../data/shared/v1beta1/metadata.yaml"
104+
replacements:
105+
- old: "imagePullPolicy: Always"
106+
new: "imagePullPolicy: IfNotPresent"
107+
- old: --metrics-addr=127.0.0.1:8080
108+
new: --metrics-addr=:8080
95109
- name: openstack
96110
type: InfrastructureProvider
97111
versions:
@@ -134,6 +148,7 @@ providers:
134148
- sourcePath: "../data/shared/v1beta1_provider/metadata.yaml"
135149
- sourcePath: "./infrastructure-openstack-no-artifact/cluster-template.yaml"
136150
- sourcePath: "./infrastructure-openstack-no-artifact/cluster-template-without-lb.yaml"
151+
- sourcePath: "../../../../templates/cluster-template-hcp.yaml"
137152
replacements:
138153
- old: gcr.io/k8s-staging-capi-openstack/capi-openstack-controller:dev
139154
new: gcr.io/k8s-staging-capi-openstack/capi-openstack-controller:e2e
@@ -233,3 +248,6 @@ intervals:
233248
default/wait-machine-remediation: ["10m", "10s"]
234249
default/wait-image-create: ["15m", "10s"]
235250
default/wait-image-delete: ["2m", "10s"]
251+
hcp/wait-cluster: ["30m", "10s"]
252+
hcp/wait-control-plane: ["45m", "10s"]
253+
hcp/wait-worker-nodes: ["45m", "10s"]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
2+
kind: KamajiControlPlane
3+
metadata:
4+
name: ${CLUSTER_NAME}-control-plane
5+
labels:
6+
clusterctl.cluster.x-k8s.io/move: ""
7+
spec:
8+
dataStore: default
9+
addons:
10+
coreDNS: {}
11+
kubeProxy: {}
12+
kubelet:
13+
cgroupfs: systemd
14+
preferredAddressTypes:
15+
- ExternalIP
16+
- InternalIP
17+
network:
18+
serviceType: LoadBalancer
19+
ingress:
20+
hostname: ${CLUSTER_NAME}-lb.${OPENSTACK_DNS_NAMESERVERS}
21+
certSANs:
22+
- ${CLUSTER_NAME}-lb.${OPENSTACK_DNS_NAMESERVERS}
23+
deployment:
24+
replicas: 1
25+
resources:
26+
requests:
27+
cpu: 250m
28+
memory: 512Mi
29+
limits:
30+
cpu: 500m
31+
memory: 1Gi
32+
extraArgs:
33+
# Enable Konnectivity for worker node communication
34+
- --enable-aggregator-routing=true
35+
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
36+
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
37+
# Add annotations for OpenStack cloud provider
38+
annotations:
39+
cluster.x-k8s.io/cluster-name: ${CLUSTER_NAME}
40+
cluster.x-k8s.io/cluster-namespace: ${NAMESPACE}
41+
version: ${KUBERNETES_VERSION}

0 commit comments

Comments
 (0)