diff --git a/cmd/cluster/openstack/create.go b/cmd/cluster/openstack/create.go index 9e57ad6cf8..aee176b75f 100644 --- a/cmd/cluster/openstack/create.go +++ b/cmd/cluster/openstack/create.go @@ -217,15 +217,15 @@ func (o *CreateOptions) GenerateResources() ([]client.Object, error) { Namespace: o.namespace, Name: "capi-provider-role", }, - // The following rule is required for CAPO to watch for the Images resources created by ORC, + // The following rule is required for CAPO to reconcile for the Images resources created by ORC, // which is a dependency since CAPO v0.11.0. // This rule is also defined in the Hypershift HostedCluster controller and the Hypershift Operator when creating // the cluster. Rules: []rbacv1.PolicyRule{ { APIGroups: []string{"openstack.k-orc.cloud"}, - Resources: []string{"images"}, - Verbs: []string{"list", "watch"}, + Resources: []string{"images", "images/status"}, + Verbs: []string{rbacv1.VerbAll}, }, }, }) diff --git a/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_default_creation_flags.yaml b/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_default_creation_flags.yaml index c0125fc305..ef92f53fa0 100644 --- a/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_default_creation_flags.yaml +++ b/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_default_creation_flags.yaml @@ -40,9 +40,9 @@ rules: - openstack.k-orc.cloud resources: - images + - images/status verbs: - - list - - watch + - '*' --- apiVersion: v1 data: diff --git a/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_minimal_flags_necessary_to_render.yaml b/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_minimal_flags_necessary_to_render.yaml index ab50bc8e0a..79d1af76f1 100644 --- a/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_minimal_flags_necessary_to_render.yaml +++ b/cmd/cluster/openstack/testdata/zz_fixture_TestCreateCluster_minimal_flags_necessary_to_render.yaml @@ -40,9 +40,9 @@ rules: - openstack.k-orc.cloud resources: - images + - images/status verbs: - - list - - watch + - '*' --- apiVersion: v1 data: diff --git a/cmd/install/assets/hypershift_operator.go b/cmd/install/assets/hypershift_operator.go index 770d7b1103..254908239a 100644 --- a/cmd/install/assets/hypershift_operator.go +++ b/cmd/install/assets/hypershift_operator.go @@ -1169,14 +1169,14 @@ func (o HyperShiftOperatorClusterRole) Build() *rbacv1.ClusterRole { Resources: []string{"ipaddresses", "ipaddresses/status"}, Verbs: []string{"create", "delete", "get", "list", "update", "watch"}, }, - // The following rule is required for CAPO to watch for the Images resources created by ORC, + // The following rule is required for CAPO to reconcile for the Images resources created by ORC, // which is a dependency since CAPO v0.11.0. // This rule is also defined in the Hypershift HostedCluster controller and the Hypershift CLI when creating // the cluster. { APIGroups: []string{"openstack.k-orc.cloud"}, - Resources: []string{"images"}, - Verbs: []string{"list", "watch"}, + Resources: []string{"images", "images/status"}, + Verbs: []string{rbacv1.VerbAll}, }, { // This allows the kubevirt csi driver to hotplug volumes to KubeVirt VMs. APIGroups: []string{"subresources.kubevirt.io"}, diff --git a/cmd/nodepool/openstack/create.go b/cmd/nodepool/openstack/create.go index 2947be7321..3196f83889 100644 --- a/cmd/nodepool/openstack/create.go +++ b/cmd/nodepool/openstack/create.go @@ -69,15 +69,6 @@ func (o *RawOpenStackPlatformCreateOptions) Validate() (*ValidatedOpenStackPlatf return nil, fmt.Errorf("flavor is required") } - // TODO(emilien): Remove that validation once we support using the image from the release payload. - // This will be possible when CAPO supports managing images in the OpenStack cluster: - // https://github.com/kubernetes-sigs/cluster-api-provider-openstack/pull/2130 - // For 4.17 we might leave this as is and let the user provide the image name as - // we plan to deliver the OpenStack provider as a dev preview. - if o.ImageName == "" { - return nil, fmt.Errorf("image name is required") - } - additionalports, err := convertAdditionalPorts(o.AdditionalPorts) if err != nil { return nil, err @@ -99,7 +90,7 @@ func BindOptions(opts *RawOpenStackPlatformCreateOptions, flags *pflag.FlagSet) func bindCoreOptions(opts *RawOpenStackPlatformCreateOptions, flags *pflag.FlagSet) { flags.StringVar(&opts.Flavor, "openstack-node-flavor", opts.Flavor, "The flavor to use for the nodepool (required)") - flags.StringVar(&opts.ImageName, "openstack-node-image-name", opts.ImageName, "The image name to use for the nodepool (required)") + flags.StringVar(&opts.ImageName, "openstack-node-image-name", opts.ImageName, "The image name to use for the nodepool (optional)") flags.StringVar(&opts.AvailabityZone, "openstack-node-availability-zone", opts.AvailabityZone, "The availability zone to use for the nodepool (optional)") flags.StringArrayVar(&opts.AdditionalPorts, "openstack-node-additional-port", opts.AdditionalPorts, fmt.Sprintf(`Specify additional port that should be attached to the nodes, the "network-id" field should point to an existing neutron network ID and the "vnic-type" is the type of the port to create, it can be specified multiple times to attach to multiple ports. Supported parameters: %s, example: "network-id:40a355cb-596d-495c-8766-419d98cadd57,vnic-type:direct"`, cmdutil.Supported(PortSpec{}))) } diff --git a/cmd/nodepool/openstack/create_test.go b/cmd/nodepool/openstack/create_test.go index 9e43415815..e92a6d3c74 100644 --- a/cmd/nodepool/openstack/create_test.go +++ b/cmd/nodepool/openstack/create_test.go @@ -15,21 +15,10 @@ func TestRawOpenStackPlatformCreateOptions_Validate(t *testing.T) { { name: "should fail if flavor is missing", input: RawOpenStackPlatformCreateOptions{ - OpenStackPlatformOptions: &OpenStackPlatformOptions{ - ImageName: "rhcos", - }, + OpenStackPlatformOptions: &OpenStackPlatformOptions{}, }, expectedError: "flavor is required", }, - { - name: "should fail if image name is missing", - input: RawOpenStackPlatformCreateOptions{ - OpenStackPlatformOptions: &OpenStackPlatformOptions{ - Flavor: "flavor", - }, - }, - expectedError: "image name is required", - }, { name: "should pass when AZ is provided", input: RawOpenStackPlatformCreateOptions{ diff --git a/docs/content/how-to/openstack/create-openstack-cluster.md b/docs/content/how-to/openstack/create-openstack-cluster.md index 2719ca65ec..7e0c1c23e9 100644 --- a/docs/content/how-to/openstack/create-openstack-cluster.md +++ b/docs/content/how-to/openstack/create-openstack-cluster.md @@ -28,7 +28,6 @@ Upon scaling up a NodePool, a Machine will be created, and the CAPI provider wil * OpenStack Octavia service must be running in the cloud hosting the guest cluster when ingress is configured with an Octavia load balancer. In the future, we'll explore other Ingress options like MetalLB. * The default external network (on which the kube-apiserver LoadBalancer type service is created) of the Management OCP cluster must be reachable from the guest cluster. -* The RHCOS image must be uploaded to OpenStack. ### Install the HyperShift and HCP CLI @@ -84,23 +83,6 @@ operator-755d587f44-lrtrq 1/1 Running 0 114s operator-755d587f44-qj6pz 1/1 Running 0 114s ``` -### Upload RHCOS image in OpenStack - -For now, we need to manually push an RHCOS image that will be used when deploying the node pools -on OpenStack. In the [future](https://issues.redhat.com/browse/OSASINFRA-3492), the CAPI provider (CAPO) will handle the RHCOS image -lifecycle by using the image available in the chosen release payload. - -Here is an example of how to upload an RHCOS image to OpenStack: - -```shell -openstack image create --disk-format qcow2 --file rhcos-openstack.x86_64.qcow2 rhcos -``` - -!!! note - - The `rhcos-openstack.x86_64.qcow2` file is the RHCOS image that was downloaded from the OpenShift mirror. - You can download the latest RHCOS image from the [Red Hat OpenShift Container Platform mirror](https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/). - ## Create a floating IP for the Ingress (optional) To get Ingress healthy in a HostedCluster without manual intervention, you need to create a floating IP that will be used by the Ingress service. @@ -168,7 +150,6 @@ hcp create cluster openstack \ --openstack-credentials-file $CLOUDS_YAML \ --openstack-ca-cert-file $CA_CERT_PATH \ --openstack-external-network-id $EXTERNAL_NETWORK_ID \ ---openstack-node-image-name $IMAGE_NAME \ --openstack-node-flavor $FLAVOR \ --openstack-ingress-floating-ip $INGRESS_FLOATING_IP ``` @@ -313,7 +294,7 @@ port for SR-IOV, with no port security and address pairs: ```shell export NODEPOOL_NAME=$CLUSTER_NAME-extra-az export WORKER_COUNT="2" -export IMAGE_NAME="rhcos" +export IMAGE_NAME="rhcos" # Pre-existing Glance image used for this NodePool export FLAVOR="m1.xlarge" export AZ="az1" export SRIOV_NEUTRON_NETWORK_ID="f050901b-11bc-4a75-a553-878509255760" diff --git a/hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack.go b/hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack.go index d06ad94805..f1730ca2a6 100644 --- a/hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack.go +++ b/hypershift-operator/controllers/hostedcluster/internal/platform/openstack/openstack.go @@ -197,7 +197,9 @@ func (a OpenStack) CAPIProviderDeploymentSpec(hcluster *hyperv1.HostedCluster, _ "--namespace=$(MY_NAMESPACE)", "--leader-elect", "--metrics-bind-addr=127.0.0.1:8080", - "--v=2", + // We need to set the log level to 3 to get the logs from ORC. + // Once ORC follows logging guidelines, we should use V(2) again. + "--v=3", }, Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ @@ -337,14 +339,14 @@ func (a OpenStack) CAPIProviderPolicyRules() []rbacv1.PolicyRule { Resources: []string{"ipaddresses", "ipaddresses/status"}, Verbs: []string{"create", "delete", "get", "list", "update", "watch"}, }, - // The following rule is required for CAPO to watch for the Images resources created by ORC, + // The following rule is required for CAPO to reconcile the Images resources created by ORC, // which is a dependency since CAPO v0.11.0. // This rule is also defined in the Hypershift Operator and the Hypershift CLI when creating // the cluster. { APIGroups: []string{"openstack.k-orc.cloud"}, - Resources: []string{"images"}, - Verbs: []string{"list", "watch"}, + Resources: []string{"images", "images/status"}, + Verbs: []string{rbacv1.VerbAll}, }, } } diff --git a/hypershift-operator/controllers/nodepool/capi.go b/hypershift-operator/controllers/nodepool/capi.go index 9386d54e5f..2448a9bec4 100644 --- a/hypershift-operator/controllers/nodepool/capi.go +++ b/hypershift-operator/controllers/nodepool/capi.go @@ -67,6 +67,13 @@ func (c *CAPI) Reconcile(ctx context.Context) error { return err } + // Reconcile ORC resources + if nodePool.Spec.Platform.Type == hyperv1.OpenStackPlatform { + if err := c.reconcileORCResources(ctx); err != nil { + return err + } + } + // Reconcile (Platform)MachineTemplate. template, mutateTemplate, _, err := c.machineTemplateBuilders() if err != nil { @@ -1316,3 +1323,7 @@ func (r *NodePoolReconciler) getMachinesForNodePool(ctx context.Context, nodePoo return sortedByCreationTimestamp(machinesForNodePool), nil } + +func (c *CAPI) reconcileORCResources(ctx context.Context) error { + return openstack.ReconcileOpenStackImageCR(ctx, c.Client, c.CreateOrUpdate, c.hostedCluster, c.releaseImage) +} diff --git a/hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go b/hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go index f0cf9d10fe..977d5526b0 100644 --- a/hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go +++ b/hypershift-operator/controllers/nodepool/kubevirt/kubevirt.go @@ -54,27 +54,6 @@ func defaultImage(releaseImage *releaseinfo.ReleaseImage) (string, string, error return containerImage, split[1], nil } -func unsupportedOpenstackDefaultImage(releaseImage *releaseinfo.ReleaseImage) (string, string, error) { - arch, foundArch := releaseImage.StreamMetadata.Architectures["x86_64"] - if !foundArch { - return "", "", fmt.Errorf("couldn't find OS metadata for architecture %q", "x64_64") - } - openStack, exists := arch.Artifacts["openstack"] - if !exists { - return "", "", fmt.Errorf("couldn't find OS metadata for openstack") - } - artifact, exists := openStack.Formats["qcow2.gz"] - if !exists { - return "", "", fmt.Errorf("couldn't find OS metadata for openstack qcow2.gz") - } - disk, exists := artifact["disk"] - if !exists { - return "", "", fmt.Errorf("couldn't find OS metadata for the openstack qcow2.gz disk") - } - - return disk.Location, disk.SHA256, nil -} - func allowUnsupportedRHCOSVariants(nodePool *hyperv1.NodePool) bool { val, exists := nodePool.Annotations[hyperv1.AllowUnsupportedKubeVirtRHCOSVariantsAnnotation] if exists && strings.ToLower(val) == "true" { @@ -101,7 +80,7 @@ func GetImage(nodePool *hyperv1.NodePool, releaseImage *releaseinfo.ReleaseImage imageName, imageHash, err := defaultImage(releaseImage) if err != nil && allowUnsupportedRHCOSVariants(nodePool) { - imageName, imageHash, err = unsupportedOpenstackDefaultImage(releaseImage) + imageName, imageHash, err = releaseinfo.UnsupportedOpenstackDefaultImage(releaseImage) if err != nil { return nil, err } diff --git a/hypershift-operator/controllers/nodepool/openstack/openstack.go b/hypershift-operator/controllers/nodepool/openstack/openstack.go index d4e24e63de..470fcb4c77 100644 --- a/hypershift-operator/controllers/nodepool/openstack/openstack.go +++ b/hypershift-operator/controllers/nodepool/openstack/openstack.go @@ -1,11 +1,18 @@ package openstack import ( + "context" "fmt" hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + orc "github.com/k-orc/openstack-resource-controller/api/v1alpha1" "github.com/openshift/hypershift/support/openstackutil" + "github.com/openshift/hypershift/support/releaseinfo" + "github.com/openshift/hypershift/support/upsert" "k8s.io/utils/ptr" capiopenstackv1beta1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1" ) @@ -20,12 +27,9 @@ func MachineTemplateSpec(hcluster *hyperv1.HostedCluster, nodePool *hyperv1.Node Name: ptr.To(nodePool.Spec.Platform.OpenStack.ImageName), } } else { - // TODO(emilien): Add support for using the image from the release payload. - // This will be possible when CAPO supports managing images in the OpenStack cluster: - // https://github.com/kubernetes-sigs/cluster-api-provider-openstack/pull/2130 - // For 4.17 we might leave this as is and let the user provide the image name as - // we plan to deliver the OpenStack provider as a dev preview. - return nil, fmt.Errorf("image name is required") + openStackMachineTemplate.Template.Spec.Image.ImageRef = &capiopenstackv1beta1.ResourceReference{ + Name: "rhcos-" + hcluster.Name, + } } // TODO: add support for BYO network/subnet @@ -72,3 +76,62 @@ func MachineTemplateSpec(hcluster *hyperv1.HostedCluster, nodePool *hyperv1.Node } return openStackMachineTemplate, nil } + +func ReconcileOpenStackImageCR(ctx context.Context, client client.Client, createOrUpdate upsert.CreateOrUpdateFN, hcluster *hyperv1.HostedCluster, release *releaseinfo.ReleaseImage) error { + openStackImage := orc.Image{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rhcos-" + hcluster.Name, + Namespace: hcluster.Namespace, + // TODO: add proper cleanup in CAPI resources cleanup + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: hcluster.APIVersion, + Kind: hcluster.Kind, + Name: hcluster.Name, + UID: hcluster.UID, + }, + }, + }, + Spec: orc.ImageSpec{}, + } + + if _, err := createOrUpdate(ctx, client, &openStackImage, func() error { + err := reconcileOpenStackImageSpec(hcluster, &openStackImage.Spec, release) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + return nil +} + +func reconcileOpenStackImageSpec(hcluster *hyperv1.HostedCluster, openStackImageSpec *orc.ImageSpec, release *releaseinfo.ReleaseImage) error { + imageURL, imageHash, err := releaseinfo.UnsupportedOpenstackDefaultImage(release) + if err != nil { + return fmt.Errorf("failed to lookup RHCOS image: %w", err) + } + + openStackImageSpec.CloudCredentialsRef = orc.CloudCredentialsReference{ + SecretName: hcluster.Spec.Platform.OpenStack.IdentityRef.Name, + CloudName: hcluster.Spec.Platform.OpenStack.IdentityRef.CloudName, + } + + openStackImageSpec.Resource = &orc.ImageResourceSpec{ + Name: "rhcos-" + hcluster.Name, + Content: &orc.ImageContent{ + DiskFormat: "qcow2", + Download: &orc.ImageContentSourceDownload{ + URL: imageURL, + Decompress: ptr.To(orc.ImageCompressionGZ), + Hash: &orc.ImageHash{ + Algorithm: "sha256", + Value: imageHash, + }, + }, + }, + } + + return nil +} diff --git a/hypershift-operator/controllers/nodepool/openstack/openstack_test.go b/hypershift-operator/controllers/nodepool/openstack/openstack_test.go index 3e93c18a92..0d877a09fd 100644 --- a/hypershift-operator/controllers/nodepool/openstack/openstack_test.go +++ b/hypershift-operator/controllers/nodepool/openstack/openstack_test.go @@ -106,29 +106,6 @@ func TestOpenStackMachineTemplate(t *testing.T) { }, }, }, - { - name: "missing image name", - nodePool: hyperv1.NodePoolSpec{ - ClusterName: "", - Replicas: nil, - Config: nil, - Management: hyperv1.NodePoolManagement{}, - AutoScaling: nil, - Platform: hyperv1.NodePoolPlatform{ - Type: hyperv1.OpenStackPlatform, - OpenStack: &hyperv1.OpenStackNodePoolPlatform{ - Flavor: flavor, - }, - }, - Release: hyperv1.Release{}, - }, - - checkError: func(t *testing.T, err error) { - if err == nil { - t.Errorf("image name is required") - } - }, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/support/releaseinfo/releaseinfo.go b/support/releaseinfo/releaseinfo.go index 88cc633093..b875779243 100644 --- a/support/releaseinfo/releaseinfo.go +++ b/support/releaseinfo/releaseinfo.go @@ -355,3 +355,24 @@ func (v ComponentVersions) DisplayNameLabel() string { } return buf.String() } + +func UnsupportedOpenstackDefaultImage(releaseImage *ReleaseImage) (string, string, error) { + arch, foundArch := releaseImage.StreamMetadata.Architectures["x86_64"] + if !foundArch { + return "", "", fmt.Errorf("couldn't find OS metadata for architecture %q", "x64_64") + } + openStack, exists := arch.Artifacts["openstack"] + if !exists { + return "", "", fmt.Errorf("couldn't find OS metadata for openstack") + } + artifact, exists := openStack.Formats["qcow2.gz"] + if !exists { + return "", "", fmt.Errorf("couldn't find OS metadata for openstack qcow2.gz") + } + disk, exists := artifact["disk"] + if !exists { + return "", "", fmt.Errorf("couldn't find OS metadata for the openstack qcow2.gz disk") + } + + return disk.Location, disk.SHA256, nil +} diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index ffffdb70a6..29eeb92c79 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -561,8 +561,7 @@ func (p *options) DefaultOpenStackOptions() hypershiftopenstack.RawCreateOptions OpenStackExternalNetworkID: p.configurableClusterOptions.OpenStackExternalNetworkID, NodePoolOpts: &openstacknodepool.RawOpenStackPlatformCreateOptions{ OpenStackPlatformOptions: &openstacknodepool.OpenStackPlatformOptions{ - Flavor: p.configurableClusterOptions.OpenStackNodeFlavor, - ImageName: p.configurableClusterOptions.OpenStackNodeImageName, + Flavor: p.configurableClusterOptions.OpenStackNodeFlavor, }, }, } diff --git a/test/e2e/nodepool_osp_image_test.go b/test/e2e/nodepool_osp_image_test.go new file mode 100644 index 0000000000..a3a7aeefbc --- /dev/null +++ b/test/e2e/nodepool_osp_image_test.go @@ -0,0 +1,124 @@ +//go:build e2e +// +build e2e + +package e2e + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1" + "github.com/openshift/hypershift/hypershift-operator/controllers/manifests" + e2eutil "github.com/openshift/hypershift/test/e2e/util" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + capiopenstackv1alpha1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha1" + capiopenstackv1beta1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1" + capiv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util" + crclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +type OpenStackImageTest struct { + DummyInfraSetup + ctx context.Context + managementClient crclient.Client + hostedCluster *hyperv1.HostedCluster + hostedControlPlaneNamespace string +} + +func NewOpenStackImageTest(ctx context.Context, mgmtClient crclient.Client, hostedCluster *hyperv1.HostedCluster) *OpenStackImageTest { + return &OpenStackImageTest{ + ctx: ctx, + hostedCluster: hostedCluster, + managementClient: mgmtClient, + hostedControlPlaneNamespace: manifests.HostedControlPlaneNamespace(hostedCluster.Namespace, hostedCluster.Name), + } +} + +func (o OpenStackImageTest) Setup(t *testing.T) { + t.Log("Starting test OpenStackImageTest") + + if globalOpts.Platform != hyperv1.OpenStackPlatform { + t.Skip("test only supported on platform OpenStack") + } + + if globalOpts.configurableClusterOptions.OpenStackNodeImageName == "" { + t.Skip("OpenStack image name not provided, skipping test") + } +} + +func (o OpenStackImageTest) Run(t *testing.T, nodePool hyperv1.NodePool, _ []corev1.Node) { + np := &hyperv1.NodePool{} + e2eutil.EventuallyObject(t, o.ctx, "NodePool to have image configured", + func(ctx context.Context) (*hyperv1.NodePool, error) { + err := o.managementClient.Get(ctx, util.ObjectKey(&nodePool), np) + return np, err + }, + []e2eutil.Predicate[*hyperv1.NodePool]{ + func(nodePool *hyperv1.NodePool) (done bool, reasons string, err error) { + want, got := hyperv1.OpenStackPlatform, nodePool.Spec.Platform.Type + return want == got, fmt.Sprintf("expected NodePool to have platform %s, got %s", want, got), nil + }, + func(pool *hyperv1.NodePool) (done bool, reasons string, err error) { + diff := cmp.Diff(globalOpts.configurableClusterOptions.OpenStackNodeImageName, ptr.Deref(np.Spec.Platform.OpenStack, hyperv1.OpenStackNodePoolPlatform{}).ImageName) + return diff == "", fmt.Sprintf("incorrect image name: %v", diff), nil + }, + }, + ) + + e2eutil.EventuallyObjects(t, o.ctx, "OpenStackServers to be created with the correct image", + func(ctx context.Context) ([]*capiopenstackv1beta1.OpenStackMachine, error) { + list := &capiopenstackv1beta1.OpenStackMachineList{} + err := o.managementClient.List(ctx, list, crclient.InNamespace(o.hostedControlPlaneNamespace), crclient.MatchingLabels{capiv1.MachineDeploymentNameLabel: nodePool.Name}) + oms := make([]*capiopenstackv1beta1.OpenStackMachine, len(list.Items)) + for i := range list.Items { + oms[i] = &list.Items[i] + } + return oms, err + }, + []e2eutil.Predicate[[]*capiopenstackv1beta1.OpenStackMachine]{ + func(machines []*capiopenstackv1beta1.OpenStackMachine) (done bool, reasons string, err error) { + return len(machines) == int(*nodePool.Spec.Replicas), fmt.Sprintf("expected %d OpenStackMachines, got %d", *nodePool.Spec.Replicas, len(machines)), nil + }, + }, + []e2eutil.Predicate[*capiopenstackv1beta1.OpenStackMachine]{ + func(machine *capiopenstackv1beta1.OpenStackMachine) (done bool, reasons string, err error) { + server := &capiopenstackv1alpha1.OpenStackServer{} + err = o.managementClient.Get(o.ctx, crclient.ObjectKey{Name: machine.Name, Namespace: o.hostedControlPlaneNamespace}, server) + if err != nil { + return false, "", err + } + if server.Spec.Image.Filter != nil && *server.Spec.Image.Filter.Name != globalOpts.configurableClusterOptions.OpenStackNodeImageName { + return false, fmt.Sprintf("expected image name %s, got %s", globalOpts.configurableClusterOptions.OpenStackNodeImageName, *server.Spec.Image.Filter.Name), nil + } + return true, "", nil + }, + }, + ) +} + +func (o OpenStackImageTest) BuildNodePoolManifest(defaultNodepool hyperv1.NodePool) (*hyperv1.NodePool, error) { + nodePool := &hyperv1.NodePool{ + ObjectMeta: metav1.ObjectMeta{ + Name: o.hostedCluster.Name + "-" + "test-osp-image", + Namespace: o.hostedCluster.Namespace, + }, + } + defaultNodepool.Spec.DeepCopyInto(&nodePool.Spec) + + nodePool.Spec.Replicas = &oneReplicas + nodePool.Spec.Platform.OpenStack.ImageName = globalOpts.configurableClusterOptions.OpenStackNodeImageName + return nodePool, nil +} + +func (o OpenStackImageTest) SetupInfra(t *testing.T) error { + return nil +} + +func (o OpenStackImageTest) TeardownInfra(t *testing.T) error { + return nil +} diff --git a/test/e2e/nodepool_test.go b/test/e2e/nodepool_test.go index 18fbb4518f..5dac198974 100644 --- a/test/e2e/nodepool_test.go +++ b/test/e2e/nodepool_test.go @@ -125,6 +125,10 @@ func TestNodePool(t *testing.T) { name: "OpenStackAZTest", test: NewOpenStackAZTest(ctx, mgtClient, hostedCluster), }, + { + name: "OpenStackImageTest", + test: NewOpenStackImageTest(ctx, mgtClient, hostedCluster), + }, { name: "TestMirrorConfigs", test: NewMirrorConfigsTest(ctx, mgtClient, hostedCluster, hostedClusterClient),