Skip to content

Commit cc4847f

Browse files
committed
add initial pass for e2e test suite for hcp
Signed-off-by: Bharath Nallapeta <[email protected]>
1 parent d9c1a62 commit cc4847f

File tree

14 files changed

+1109
-1
lines changed

14 files changed

+1109
-1
lines changed

Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ e2e-templates: $(addprefix $(E2E_NO_ARTIFACT_TEMPLATES_DIR)/, \
184184
cluster-template-flatcar.yaml \
185185
cluster-template-k8s-upgrade.yaml \
186186
cluster-template-flatcar-sysext.yaml \
187-
cluster-template-no-bastion.yaml)
187+
cluster-template-no-bastion.yaml \
188+
cluster-template-hcp-management.yaml \
189+
cluster-template-hcp-workload.yaml \
190+
cluster-template-hcp-broken.yaml)
188191
# Currently no templates that require CI artifacts
189192
# $(addprefix $(E2E_TEMPLATES_DIR)/, add-templates-here.yaml) \
190193
@@ -236,6 +239,16 @@ test-conformance: $(GINKGO) e2e-prerequisites ## Run clusterctl based conformanc
236239
test-conformance-fast: ## Run clusterctl based conformance test on workload cluster (requires Docker) using a subset of the conformance suite in parallel.
237240
$(MAKE) test-conformance CONFORMANCE_E2E_ARGS="-kubetest.config-file=$(KUBETEST_FAST_CONF_PATH) -kubetest.ginkgo-nodes=5 $(E2E_ARGS)"
238241

242+
HCP_E2E_ARGS ?=
243+
HCP_E2E_ARGS += $(E2E_ARGS)
244+
.PHONY: test-hcp
245+
test-hcp: $(GINKGO) e2e-prerequisites ## Run HCP (Hosted Control Plane) e2e tests
246+
time $(GINKGO) -fail-fast -trace -timeout=3h -show-node-events -v -tags=e2e -nodes=$(E2E_GINKGO_PARALLEL) \
247+
--output-dir="$(ARTIFACTS)" --junit-report="junit.hcp_suite.1.xml" \
248+
-focus="$(E2E_GINKGO_FOCUS)" $(_SKIP_ARGS) $(E2E_GINKGO_ARGS) ./test/e2e/suites/hcp/... -- \
249+
-config-path="$(E2E_CONF_PATH)" -artifacts-folder="$(ARTIFACTS)" \
250+
-data-folder="$(E2E_DATA_DIR)" $(HCP_E2E_ARGS)
251+
239252

240253

241254
APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main)

controllers/openstackmachine_controller_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,153 @@ func TestGetPortIDs(t *testing.T) {
261261
})
262262
}
263263
}
264+
265+
func TestOpenStackMachineSpecToOpenStackServerSpec_NilNetworkCases(t *testing.T) {
266+
identityRef := infrav1.OpenStackIdentityReference{
267+
Name: "foo",
268+
CloudName: "my-cloud",
269+
}
270+
image := infrav1.ImageParam{Filter: &infrav1.ImageFilter{Name: ptr.To("my-image")}}
271+
tags := []string{"tag1", "tag2"}
272+
userData := &corev1.LocalObjectReference{Name: "server-data-secret"}
273+
274+
tests := []struct {
275+
name string
276+
openStackCluster *infrav1.OpenStackCluster
277+
spec *infrav1.OpenStackMachineSpec
278+
expectedDefaultNetID string
279+
expectedPorts []infrav1.PortOpts
280+
description string
281+
}{
282+
{
283+
name: "Empty cluster network ID, machine defines explicit ports",
284+
openStackCluster: &infrav1.OpenStackCluster{
285+
Spec: infrav1.OpenStackClusterSpec{
286+
ManagedSecurityGroups: &infrav1.ManagedSecurityGroups{},
287+
},
288+
Status: infrav1.OpenStackClusterStatus{
289+
Network: &infrav1.NetworkStatusWithSubnets{
290+
NetworkStatus: infrav1.NetworkStatus{
291+
ID: "", // Empty network ID
292+
},
293+
},
294+
WorkerSecurityGroup: &infrav1.SecurityGroupStatus{
295+
ID: workerSecurityGroupUUID,
296+
},
297+
},
298+
},
299+
spec: &infrav1.OpenStackMachineSpec{
300+
Flavor: ptr.To(flavorName),
301+
Image: image,
302+
Ports: []infrav1.PortOpts{{
303+
Network: &infrav1.NetworkParam{ID: ptr.To(networkUUID)},
304+
}},
305+
},
306+
expectedDefaultNetID: "", // Empty because cluster network ID is empty
307+
expectedPorts: []infrav1.PortOpts{{
308+
Network: &infrav1.NetworkParam{ID: ptr.To(networkUUID)},
309+
SecurityGroups: []infrav1.SecurityGroupParam{{
310+
ID: ptr.To(workerSecurityGroupUUID),
311+
}},
312+
}},
313+
description: "Should work when cluster has empty network ID but machine defines ports",
314+
},
315+
}
316+
317+
for _, tt := range tests {
318+
t.Run(tt.name, func(t *testing.T) {
319+
// Verify the default network ID extraction logic
320+
var defaultNetworkID string
321+
if tt.openStackCluster.Status.Network != nil {
322+
defaultNetworkID = tt.openStackCluster.Status.Network.ID
323+
}
324+
325+
if defaultNetworkID != tt.expectedDefaultNetID {
326+
t.Errorf("Expected defaultNetworkID = %q, got %q", tt.expectedDefaultNetID, defaultNetworkID)
327+
}
328+
329+
// Test the spec conversion
330+
var managedSecurityGroupID *string
331+
if tt.openStackCluster.Status.WorkerSecurityGroup != nil {
332+
managedSecurityGroupID = &tt.openStackCluster.Status.WorkerSecurityGroup.ID
333+
}
334+
335+
spec := openStackMachineSpecToOpenStackServerSpec(
336+
tt.spec,
337+
identityRef,
338+
tags,
339+
"", // failureDomain
340+
userData,
341+
managedSecurityGroupID,
342+
defaultNetworkID,
343+
)
344+
345+
if !reflect.DeepEqual(spec.Ports, tt.expectedPorts) {
346+
t.Errorf("Expected ports = %+v, got %+v", tt.expectedPorts, spec.Ports)
347+
}
348+
})
349+
}
350+
}
351+
352+
func TestValidateNetworkConfiguration(t *testing.T) {
353+
tests := []struct {
354+
name string
355+
clusterNetworkID string
356+
machinePortsCount int
357+
expectError bool
358+
expectedErrorMsg string
359+
description string
360+
}{
361+
{
362+
name: "Valid: cluster has network, machine has no explicit ports",
363+
clusterNetworkID: networkUUID,
364+
machinePortsCount: 0,
365+
expectError: false,
366+
description: "Should succeed when cluster provides default network",
367+
},
368+
{
369+
name: "Valid: no cluster network, machine defines explicit ports",
370+
clusterNetworkID: "",
371+
machinePortsCount: 1,
372+
expectError: false,
373+
description: "Should succeed when machine defines its own networking",
374+
},
375+
{
376+
name: "Invalid: no cluster network, no machine ports",
377+
clusterNetworkID: "",
378+
machinePortsCount: 0,
379+
expectError: true,
380+
expectedErrorMsg: "no network configured: cluster network is missing and machine spec does not define ports with a network",
381+
description: "Should fail with terminal error when no networking is configured anywhere",
382+
},
383+
{
384+
name: "Valid: cluster network and machine ports both defined",
385+
clusterNetworkID: networkUUID,
386+
machinePortsCount: 2,
387+
expectError: false,
388+
description: "Should succeed when both cluster and machine define networking",
389+
},
390+
}
391+
392+
for _, tt := range tests {
393+
t.Run(tt.name, func(t *testing.T) {
394+
// Simulate the validation logic from the controller
395+
hasClusterNetwork := tt.clusterNetworkID != ""
396+
hasMachinePorts := tt.machinePortsCount > 0
397+
398+
shouldFail := !hasClusterNetwork && !hasMachinePorts
399+
400+
if shouldFail != tt.expectError {
401+
t.Errorf("Expected error: %v, but validation result: %v", tt.expectError, shouldFail)
402+
}
403+
404+
if tt.expectError && shouldFail {
405+
// In the real controller, this would be a terminal error
406+
actualError := "no network configured: cluster network is missing and machine spec does not define ports with a network"
407+
if actualError != tt.expectedErrorMsg {
408+
t.Errorf("Expected error message: %q, got: %q", tt.expectedErrorMsg, actualError)
409+
}
410+
}
411+
})
412+
}
413+
}

pkg/cloud/services/networking/securitygroups.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"slices"
23+
"strings"
2324

2425
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/attributestags"
2526
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups"
@@ -564,6 +565,11 @@ func (s *Service) createRule(securityGroupID string, r resolvedSecurityGroupRule
564565
s.scope.Logger().V(6).Info("Creating rule", "description", r.Description, "direction", dir, "portRangeMin", r.PortRangeMin, "portRangeMax", r.PortRangeMax, "proto", proto, "etherType", etherType, "remoteGroupID", r.RemoteGroupID, "remoteIPPrefix", r.RemoteIPPrefix, "securityGroupID", securityGroupID)
565566
_, err := s.client.CreateSecGroupRule(createOpts)
566567
if err != nil {
568+
// Handle HTTP 409 (SecurityGroupRuleExists) as success - the rule already exists
569+
if strings.Contains(err.Error(), "SecurityGroupRuleExists") || strings.Contains(err.Error(), "already exists") {
570+
s.scope.Logger().V(4).Info("Security group rule already exists, treating as success", "description", r.Description, "securityGroupID", securityGroupID)
571+
return nil
572+
}
567573
return err
568574
}
569575
return nil

test/e2e/data/e2e_conf.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ providers:
9393
- old: "--leader-elect"
9494
new: "--leader-elect=false\n - --sync-period=1m"
9595

96+
9697
- name: openstack
9798
type: InfrastructureProvider
9899
versions:
@@ -182,6 +183,15 @@ variables:
182183
CNI: "../../data/cni/calico.yaml"
183184
CCM: "../../data/ccm/cloud-controller-manager.yaml"
184185
EXP_CLUSTER_RESOURCE_SET: "true"
186+
# HCP/Kamaji configuration
187+
KAMAJI_VERSION: "edge-25.7.1"
188+
KAMAJI_NAMESPACE: "kamaji-system"
189+
CLUSTER_DATASTORE: "default"
190+
HCP_SERVICE_TYPE: "LoadBalancer"
191+
HCP_CPU_LIMIT: "1000m"
192+
HCP_MEMORY_LIMIT: "1Gi"
193+
HCP_CPU_REQUEST: "100m"
194+
HCP_MEMORY_REQUEST: "300Mi"
185195
OPENSTACK_BASTION_IMAGE_NAME: "cirros-0.6.1-x86_64-disk"
186196
OPENSTACK_BASTION_IMAGE_URL: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/cirros/2022-12-05/cirros-0.6.1-x86_64-disk.img
187197
OPENSTACK_BASTION_IMAGE_HASH: 0c839612eb3f2469420f2ccae990827f
@@ -234,3 +244,4 @@ intervals:
234244
default/wait-machine-remediation: ["10m", "10s"]
235245
default/wait-image-create: ["15m", "10s"]
236246
default/wait-image-delete: ["2m", "10s"]
247+
default/wait-daemonset: ["10m", "30s"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
resources:
5+
- ../default
6+
7+
namePrefix: hcp-broken-
8+
9+
patchesStrategicMerge:
10+
- patch-broken-networking.yaml
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
# This patch removes networking configuration from OpenStackCluster
3+
# to test graceful failure scenarios when cluster network is missing
4+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
5+
kind: OpenStackCluster
6+
metadata:
7+
name: ${CLUSTER_NAME}
8+
spec:
9+
# Remove managedSubnets to simulate missing network configuration
10+
managedSubnets: []
11+
# Remove managedSecurityGroups to further test edge cases
12+
managedSecurityGroups: null
13+
# Keep other required configuration
14+
apiServerLoadBalancer:
15+
enabled: true
16+
externalNetwork:
17+
id: ${OPENSTACK_EXTERNAL_NETWORK_ID}
18+
identityRef:
19+
cloudName: ${OPENSTACK_CLOUD}
20+
name: ${CLUSTER_NAME}-cloud-config
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
# Use same base resources as default but with our own component overrides
5+
resources:
6+
- ../../../../../kustomize/v1beta1/default
7+
8+
# Override components to use existing images instead of downloading
9+
components:
10+
- ../common-patches/cluster
11+
- ../common-patches/cni
12+
- ../upgrade-patches
13+
- ../common-patches/ccm
14+
- ../common-patches/externalNetworkByName
15+
- ../common-patches/images-without-ref # Use existing images instead of ORC download
16+
17+
patchesStrategicMerge:
18+
- patch-management-cluster.yaml
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
# Patch the Cluster to ensure it has sufficient resources for HCP hosting
3+
apiVersion: cluster.x-k8s.io/v1beta1
4+
kind: Cluster
5+
metadata:
6+
name: ${CLUSTER_NAME}
7+
spec:
8+
clusterNetwork:
9+
pods:
10+
cidrBlocks:
11+
- 10.244.0.0/16 # Different CIDR to avoid conflicts with workload clusters
12+
services:
13+
cidrBlocks:
14+
- 10.96.0.0/12 # Different service CIDR
15+
---
16+
# Patch the OpenStackCluster to ensure robust networking for HCP
17+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
18+
kind: OpenStackCluster
19+
metadata:
20+
name: ${CLUSTER_NAME}
21+
spec:
22+
managedSubnets:
23+
- cidr: 10.10.0.0/24 # Larger management network
24+
dnsNameservers:
25+
- ${OPENSTACK_DNS_NAMESERVERS}
26+
managedSecurityGroups:
27+
allNodesSecurityGroupRules:
28+
- description: Created by cluster-api-provider-openstack - BGP (calico)
29+
direction: ingress
30+
etherType: IPv4
31+
name: BGP (Calico)
32+
portRangeMax: 179
33+
portRangeMin: 179
34+
protocol: tcp
35+
remoteManagedGroups:
36+
- controlplane
37+
- worker
38+
- description: Created by cluster-api-provider-openstack - IP-in-IP (calico)
39+
direction: ingress
40+
etherType: IPv4
41+
name: IP-in-IP (calico)
42+
protocol: "4"
43+
remoteManagedGroups:
44+
- controlplane
45+
- worker
46+
# Additional security group rules for HCP hosting
47+
- description: Created by cluster-api-provider-openstack - Kamaji API Server
48+
direction: ingress
49+
etherType: IPv4
50+
name: Kamaji API Server
51+
portRangeMax: 6443
52+
portRangeMin: 6443
53+
protocol: tcp
54+
remoteManagedGroups:
55+
- controlplane
56+
- worker
57+
- description: Created by cluster-api-provider-openstack - Kamaji etcd
58+
direction: ingress
59+
etherType: IPv4
60+
name: Kamaji etcd
61+
portRangeMax: 2380
62+
portRangeMin: 2379
63+
protocol: tcp
64+
remoteManagedGroups:
65+
- controlplane
66+
- worker
67+
---
68+
# Patch the control plane to use larger instances for HCP hosting
69+
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
70+
kind: KubeadmControlPlane
71+
metadata:
72+
name: ${CLUSTER_NAME}-control-plane
73+
spec:
74+
replicas: ${CONTROL_PLANE_MACHINE_COUNT:-3} # Default to 3 for HA
75+
---
76+
# Patch the control plane machine template for larger flavor
77+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
78+
kind: OpenStackMachineTemplate
79+
metadata:
80+
name: ${CLUSTER_NAME}-control-plane
81+
spec:
82+
template:
83+
spec:
84+
flavor: ${OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR:-m1.large} # Larger default for HCP hosting

0 commit comments

Comments
 (0)