Skip to content

Commit 560e5ff

Browse files
committed
misc fixes
Signed-off-by: Bharath Nallapeta <[email protected]>
1 parent 523d934 commit 560e5ff

File tree

3 files changed

+189
-45
lines changed

3 files changed

+189
-45
lines changed

hack/install-kamaji.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
set -euo pipefail
1818

19+
KUBECONFIG_ARG=""
20+
if [ -n "${1-}" ]; then
21+
echo "Using kubeconfig: ${1}"
22+
KUBECONFIG_ARG="--kubeconfig ${1}"
23+
fi
24+
1925
# Simple Kamaji installation using Helm
2026
# This script installs Kamaji v1.0.0 in the management cluster
2127

@@ -35,15 +41,16 @@ helm install kamaji clastix/kamaji \
3541
--version ${KAMAJI_VERSION} \
3642
--namespace ${KAMAJI_NAMESPACE} \
3743
--create-namespace \
44+
${KUBECONFIG_ARG} \
3845
--wait
3946

4047
# Verify installation
4148
echo "Verifying Kamaji installation..."
42-
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=kamaji -n ${KAMAJI_NAMESPACE} --timeout=300s
49+
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=kamaji -n ${KAMAJI_NAMESPACE} --timeout=300s ${KUBECONFIG_ARG}
4350

4451
# Create default datastore
4552
echo "Creating default datastore..."
46-
cat <<EOF | kubectl apply -f -
53+
cat <<EOF | kubectl apply -f - ${KUBECONFIG_ARG}
4754
apiVersion: kamaji.clastix.io/v1alpha1
4855
kind: DataStore
4956
metadata:

test/e2e/suites/hcp/hcp_suite_test.go

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"fmt"
2525
"os/exec"
2626
"path/filepath"
27-
"strings"
2827
"testing"
2928
"time"
3029

@@ -33,9 +32,14 @@ import (
3332
ctrl "sigs.k8s.io/controller-runtime"
3433

3534
corev1 "k8s.io/api/core/v1"
35+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36+
"k8s.io/apimachinery/pkg/types"
3637
"k8s.io/utils/ptr"
38+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3739
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
40+
"sigs.k8s.io/controller-runtime/pkg/client"
3841

42+
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
3943
"sigs.k8s.io/cluster-api-provider-openstack/test/e2e/shared"
4044
)
4145

@@ -118,50 +122,18 @@ var _ = Describe("Hosted Control Plane tests", func() {
118122
workloadCluster := e2eCtx.Environment.BootstrapClusterProxy.GetWorkloadCluster(ctx, namespace.Name, managementClusterName)
119123
managementKubeconfig := workloadCluster.GetKubeconfigPath()
120124

121-
By("Installing Kamaji v1.0.0 on management cluster using Helm")
122-
shared.Logf("Installing Kamaji v1.0.0 on management cluster")
123-
124-
// Install Kamaji using Helm
125-
cmd := exec.Command("helm", "repo", "add", "clastix", "https://clastix.github.io/charts")
126-
output, err := cmd.CombinedOutput()
127-
shared.Logf("Helm repo add output: %s", string(output))
128-
Expect(err).ToNot(HaveOccurred(), "Failed to add Clastix Helm repo: %v", err)
129-
130-
cmd = exec.Command("helm", "repo", "update")
131-
output, err = cmd.CombinedOutput()
132-
shared.Logf("Helm repo update output: %s", string(output))
133-
Expect(err).ToNot(HaveOccurred(), "Failed to update Helm repos: %v", err)
134-
135-
cmd = exec.Command("helm", "install", "kamaji", "clastix/kamaji",
136-
"--version", "v1.0.0",
137-
"--namespace", "kamaji-system",
138-
"--create-namespace",
139-
"--kubeconfig", managementKubeconfig,
140-
"--wait", "--timeout", "10m")
141-
output, err = cmd.CombinedOutput()
142-
shared.Logf("Kamaji installation output: %s", string(output))
143-
Expect(err).ToNot(HaveOccurred(), "Failed to install Kamaji: %v", err)
144-
145-
By("Creating default DataStore for Kamaji")
146-
datastoreYaml := `apiVersion: kamaji.clastix.io/v1alpha1
147-
kind: DataStore
148-
metadata:
149-
name: default
150-
namespace: kamaji-system
151-
spec:
152-
driver: etcd
153-
endpoints:
154-
- kamaji-etcd.kamaji-system.svc.cluster.local:2379`
155-
156-
cmd = exec.Command("kubectl", "apply", "--kubeconfig", managementKubeconfig, "-f", "-")
157-
cmd.Stdin = strings.NewReader(datastoreYaml)
158-
output, err = cmd.CombinedOutput()
159-
shared.Logf("DataStore creation output: %s", string(output))
160-
Expect(err).ToNot(HaveOccurred(), "Failed to create DataStore: %v", err)
125+
By("Installing Kamaji v1.0.0 on management cluster using shell script")
126+
shared.Logf("Installing Kamaji via hack/install-kamaji.sh on management cluster: %s", managementClusterName)
127+
128+
installCmd := exec.Command("../../../../hack/install-kamaji.sh", managementKubeconfig)
129+
output, err := installCmd.CombinedOutput()
130+
shared.Logf("Kamaji installation script output:\n%s", string(output))
131+
Expect(err).ToNot(HaveOccurred(), "Failed to install Kamaji using script")
161132

162133
By("Waiting for Kamaji to be ready")
163-
// Give Kamaji some time to initialize
164-
time.Sleep(30 * time.Second)
134+
// The script waits for pods to be ready, but a small extra delay can prevent race conditions
135+
// where the webhook is not yet fully available for the TenantControlPlane.
136+
time.Sleep(10 * time.Second)
165137

166138
By("Creating workload cluster with Kamaji control plane")
167139
shared.Logf("Creating HCP workload cluster: %s", workloadClusterName)
@@ -203,8 +175,53 @@ spec:
203175
WorkloadClusterProxy: workloadClusterProxy,
204176
Namespace: namespace.Name,
205177
ClusterName: workloadClusterName,
178+
E2EContext: e2eCtx,
206179
})
207180

181+
By("Testing terminal error for missing network")
182+
// This test intentionally creates a machine that should fail to validate the terminal error handling
183+
func() {
184+
// Get the OpenStackCluster and patch its status to remove the network
185+
openStackCluster := &infrav1.OpenStackCluster{}
186+
err := workloadClusterProxy.GetClient().Get(ctx, types.NamespacedName{Name: workloadClusterName, Namespace: namespace.Name}, openStackCluster)
187+
Expect(err).ToNot(HaveOccurred())
188+
189+
patch := client.MergeFrom(openStackCluster.DeepCopy())
190+
openStackCluster.Status.Network = nil
191+
Expect(workloadClusterProxy.GetClient().Status().Patch(ctx, openStackCluster, patch)).To(Succeed())
192+
193+
// Create a machine without a network defined in its spec. This should fail because the cluster network is also gone.
194+
machineName := fmt.Sprintf("%s-terminal-test", workloadClusterName)
195+
openStackMachine := &infrav1.OpenStackMachine{
196+
ObjectMeta: metav1.ObjectMeta{Name: machineName, Namespace: namespace.Name},
197+
Spec: infrav1.OpenStackMachineSpec{
198+
Flavor: e2eCtx.E2EConfig.Variables[shared.OpenstackNodeMachineFlavor],
199+
Image: infrav1.ImageParam{Filter: &infrav1.ImageFilter{Name: &e2eCtx.E2EConfig.Variables[shared.OpenstackImageName]}},
200+
SSHKeyName: e2eCtx.E2EConfig.Variables[shared.OpenstackSSHKeyName],
201+
},
202+
}
203+
Expect(workloadClusterProxy.GetClient().Create(ctx, openStackMachine)).To(Succeed())
204+
205+
// Assert that the machine gets the terminal error condition
206+
g := NewWithT(GinkgoT())
207+
g.Eventually(func() (bool, error) {
208+
err := workloadClusterProxy.GetClient().Get(ctx, client.ObjectKeyFromObject(openStackMachine), openStackMachine)
209+
if err != nil {
210+
return false, err
211+
}
212+
for _, condition := range openStackMachine.Status.Conditions {
213+
if condition.Type == clusterv1.ReadyCondition && condition.Status == corev1.ConditionFalse && condition.Severity == clusterv1.ConditionSeverityError && condition.Reason == infrav1.InvalidMachineSpecReason {
214+
shared.Logf("Found terminal condition with correct reason: %s", condition.Message)
215+
return true, nil
216+
}
217+
}
218+
return false, nil
219+
}, 10*time.Minute, 15*time.Second).Should(BeTrue(), "OpenStackMachine should have a terminal error condition for missing network")
220+
221+
// Clean up the test machine
222+
Expect(workloadClusterProxy.GetClient().Delete(ctx, openStackMachine)).To(Succeed())
223+
}()
224+
208225
By("Testing Konnectivity connectivity")
209226
ValidateKonectivityConnectivity(ctx, NetworkValidationInput{
210227
WorkloadClusterProxy: workloadClusterProxy,

test/e2e/suites/hcp/network_validation_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,31 @@ package hcp
2121

2222
import (
2323
"context"
24+
"fmt"
25+
"time"
2426

2527
. "github.com/onsi/ginkgo/v2"
2628
. "github.com/onsi/gomega"
2729

30+
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
2831
corev1 "k8s.io/api/core/v1"
2932
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3033
"k8s.io/apimachinery/pkg/types"
34+
"k8s.io/utils/ptr"
3135
"sigs.k8s.io/controller-runtime/pkg/client"
3236

3337
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
3438
"sigs.k8s.io/cluster-api-provider-openstack/test/e2e/shared"
39+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
40+
"sigs.k8s.io/cluster-api/test/framework"
3541
)
3642

3743
// NetworkValidationInput contains the input for network validation tests
3844
type NetworkValidationInput struct {
3945
WorkloadClusterProxy *shared.ClusterProxy
4046
Namespace string
4147
ClusterName string
48+
E2EContext *shared.E2EContext
4249
}
4350

4451
// ValidateNetworkConfiguration tests the specific network edge cases fixed in hcp-2380
@@ -54,6 +61,9 @@ func ValidateNetworkConfiguration(ctx context.Context, input NetworkValidationIn
5461
By("Testing security group precedence")
5562
validateSecurityGroupPrecedence(ctx, input)
5663

64+
By("Testing security group precedence with a live client")
65+
validateSecurityGroupPrecedenceWithClient(ctx, input)
66+
5767
By("Testing port configuration edge cases")
5868
validatePortConfigurationEdgeCases(ctx, input)
5969

@@ -179,6 +189,116 @@ func validateSecurityGroupPrecedence(ctx context.Context, input NetworkValidatio
179189
}
180190
}
181191

192+
// validateSecurityGroupPrecedenceWithClient creates a machine with a specific security group
193+
// and verifies with the OpenStack client that it's the only one applied.
194+
func validateSecurityGroupPrecedenceWithClient(ctx context.Context, input NetworkValidationInput) {
195+
shared.Logf("Validating security group precedence with a live client")
196+
197+
// Get a compute client
198+
computeClient, err := shared.NewComputeClient(input.E2EContext)
199+
Expect(err).ToNot(HaveOccurred(), "Failed to create compute client")
200+
201+
// Define a new security group to be used exclusively for this test
202+
openStackCluster := &infrav1.OpenStackCluster{}
203+
err = input.WorkloadClusterProxy.GetClient().Get(ctx, types.NamespacedName{
204+
Namespace: input.Namespace,
205+
Name: input.ClusterName,
206+
}, openStackCluster)
207+
Expect(err).ToNot(HaveOccurred(), "Failed to get OpenStackCluster")
208+
209+
sgName := "e2e-sg-override-test"
210+
sg, err := shared.CreateSecurityGroup(input.E2EContext, sgName)
211+
Expect(err).ToNot(HaveOccurred(), "Failed to create security group for test")
212+
defer func() {
213+
Expect(shared.DeleteSecurityGroup(input.E2EContext, sg.ID)).To(Succeed())
214+
}()
215+
216+
// Create a new Machine and OpenStackMachine with the override security group
217+
machineName := fmt.Sprintf("%s-sg-override", input.ClusterName)
218+
shared.Logf("Creating machine %s with security group %s", machineName, sg.Name)
219+
220+
machine := &clusterv1.Machine{
221+
ObjectMeta: metav1.ObjectMeta{
222+
Name: machineName,
223+
Namespace: input.Namespace,
224+
Labels: map[string]string{
225+
clusterv1.ClusterNameLabel: input.ClusterName,
226+
},
227+
},
228+
Spec: clusterv1.MachineSpec{
229+
ClusterName: input.ClusterName,
230+
Version: ptr.To(e2eCtx.E2EConfig.Variables[shared.KubernetesVersion]),
231+
Bootstrap: clusterv1.Bootstrap{
232+
ConfigRef: &corev1.ObjectReference{
233+
APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
234+
Kind: "KubeadmConfigTemplate",
235+
Name: fmt.Sprintf("%s-md-0", input.ClusterName),
236+
},
237+
},
238+
InfrastructureRef: corev1.ObjectReference{
239+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
240+
Kind: "OpenStackMachine",
241+
Name: machineName,
242+
},
243+
},
244+
}
245+
246+
openStackMachine := &infrav1.OpenStackMachine{
247+
ObjectMeta: metav1.ObjectMeta{
248+
Name: machineName,
249+
Namespace: input.Namespace,
250+
},
251+
Spec: infrav1.OpenStackMachineSpec{
252+
// Explicitly set the security group, overriding any cluster defaults
253+
SecurityGroups: []infrav1.SecurityGroupParam{
254+
{ID: &sg.ID},
255+
},
256+
Flavor: e2eCtx.E2EConfig.Variables[shared.OpenstackNodeMachineFlavor],
257+
Image: infrav1.ImageParam{Filter: &infrav1.ImageFilter{Name: &e2eCtx.E2EConfig.Variables[shared.OpenstackImageName]}},
258+
SSHKeyName: e2eCtx.E2EConfig.Variables[shared.OpenstackSSHKeyName],
259+
},
260+
}
261+
262+
// Create the resources
263+
err = input.WorkloadClusterProxy.GetClient().Create(ctx, openStackMachine)
264+
Expect(err).ToNot(HaveOccurred(), "Failed to create OpenStackMachine")
265+
err = input.WorkloadClusterProxy.GetClient().Create(ctx, machine)
266+
Expect(err).ToNot(HaveOccurred(), "Failed to create Machine")
267+
268+
// Wait for the machine to get an instance ID and become ready
269+
shared.Logf("Waiting for machine %s to become ready", machineName)
270+
framework.WaitForMachineReady(ctx, framework.WaitForMachineReadyInput{
271+
Getter: input.WorkloadClusterProxy.GetClient(),
272+
Cluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: input.ClusterName, Namespace: input.Namespace}},
273+
Machine: machine,
274+
Timeout: 20 * time.Minute,
275+
Intervals: e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes"),
276+
})
277+
278+
// Get the updated OpenStackMachine to find its instance ID
279+
err = input.WorkloadClusterProxy.GetClient().Get(ctx, client.ObjectKeyFromObject(openStackMachine), openStackMachine)
280+
Expect(err).ToNot(HaveOccurred(), "Failed to get updated OpenStackMachine")
281+
Expect(openStackMachine.Status.InstanceID).ToNot(BeNil(), "InstanceID should not be nil")
282+
283+
// Use the compute client to verify the security groups on the live instance
284+
shared.Logf("Verifying security groups on instance %s", *openStackMachine.Status.InstanceID)
285+
server, err := servers.Get(computeClient, *openStackMachine.Status.InstanceID).Extract()
286+
Expect(err).ToNot(HaveOccurred(), "Failed to get server details from OpenStack")
287+
288+
// Assert that the ONLY security group is the one we specified
289+
Expect(server.SecurityGroups).To(HaveLen(1), "Should only have one security group")
290+
Expect(server.SecurityGroups[0].(map[string]interface{})["name"]).To(Equal(sg.Name), "The applied security group should be the override one")
291+
292+
shared.Logf("Successfully verified security group override")
293+
294+
// Clean up the machine
295+
shared.Logf("Deleting machine %s", machineName)
296+
err = input.WorkloadClusterProxy.GetClient().Delete(ctx, machine)
297+
Expect(err).ToNot(HaveOccurred(), "Failed to delete machine")
298+
err = input.WorkloadClusterProxy.GetClient().Delete(ctx, openStackMachine)
299+
Expect(err).ToNot(HaveOccurred(), "Failed to delete OpenStackMachine")
300+
}
301+
182302
// validatePortConfigurationEdgeCases tests edge cases in port configuration
183303
func validatePortConfigurationEdgeCases(ctx context.Context, input NetworkValidationInput) {
184304
shared.Logf("Validating port configuration edge cases")

0 commit comments

Comments
 (0)