Skip to content

Commit

Permalink
Replace parameters in pool templates with the right RHCOS version
Browse files Browse the repository at this point in the history
First the OpenStack RHCOS image, belonging to the current release is
detected. Then, if `{rhcos:openstack:checksum}` or
`{rhcos:openstack:url}` are found in the machine template, they are
replaced with the corresponding detected RHCOS version on machine
creation.

A new `ValidRHCOSImage` condition is added which has the same function
like `ValidAMI` but reports the  actual detected RHCOS version. It can
later be used by a broad variety of platforms, not just AWS.

Signed-off-by: Roman Mohr <rmohr@redhat.com>
  • Loading branch information
rmohr committed Mar 8, 2022
1 parent fc8a723 commit bd2113f
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 20 deletions.
33 changes: 31 additions & 2 deletions hypershift-operator/controllers/nodepool/kubevirt.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
package nodepool

import (
"strings"

hyperv1 "github.com/openshift/hypershift/api/v1alpha1"
capikubevirt "sigs.k8s.io/cluster-api-provider-kubevirt/api/v1alpha1"
)

func kubevirtMachineTemplateSpec(nodePool *hyperv1.NodePool) *capikubevirt.KubevirtMachineTemplateSpec {
const (
rhcosOpenStackChecksumParam string = "{rhcos:openstack:checksum}"
rhcosOpenStackURLParam string = "{rhcos:openstack:url}"
)

func kubevirtMachineTemplateSpec(nodePool *hyperv1.NodePool, rhcosInfo kubevirtRHCOSDetails) *capikubevirt.KubevirtMachineTemplateSpec {

template := nodePool.Spec.Platform.Kubevirt.NodeTemplate.DeepCopy()

dataVolumeTemplates := template.Spec.DataVolumeTemplates
for i, dv := range dataVolumeTemplates {
if dv.Spec.Source != nil && dv.Spec.Source.Registry != nil &&
dv.Spec.Source.Registry.URL != nil && strings.Contains(*dv.Spec.Source.Registry.URL, rhcosOpenStackChecksumParam) {
image := strings.ReplaceAll(*dv.Spec.Source.Registry.URL, rhcosOpenStackChecksumParam, rhcosInfo.Checksum)
dataVolumeTemplates[i].Spec.Source.Registry.URL = &image
}
if dv.Spec.Source != nil && dv.Spec.Source.HTTP != nil &&
strings.Contains(dv.Spec.Source.HTTP.URL, rhcosOpenStackURLParam) {
url := strings.ReplaceAll(dv.Spec.Source.HTTP.URL, rhcosOpenStackURLParam, rhcosInfo.OpenStackDownloadURL)
dataVolumeTemplates[i].Spec.Source.HTTP.URL = url
}
}
volumes := template.Spec.Template.Spec.Volumes
for i, volume := range volumes {
if volume.ContainerDisk != nil && strings.Contains(volume.ContainerDisk.Image, rhcosOpenStackChecksumParam) {
volumes[i].ContainerDisk.Image = strings.ReplaceAll(volume.ContainerDisk.Image, rhcosOpenStackChecksumParam, rhcosInfo.Checksum)
}
}
return &capikubevirt.KubevirtMachineTemplateSpec{
Template: capikubevirt.KubevirtMachineTemplateResource{
Spec: capikubevirt.KubevirtMachineSpec{
VirtualMachineTemplate: *nodePool.Spec.Platform.Kubevirt.NodeTemplate,
VirtualMachineTemplate: *template,
},
},
}
Expand Down
6 changes: 3 additions & 3 deletions hypershift-operator/controllers/nodepool/kubevirt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestKubevirtMachineTemplate(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := kubevirtMachineTemplateSpec(tc.nodePool)
result := kubevirtMachineTemplateSpec(tc.nodePool, kubevirtRHCOSDetails{})
if !equality.Semantic.DeepEqual(tc.expected, result) {
t.Errorf(cmp.Diff(tc.expected, result))
}
Expand All @@ -69,7 +69,7 @@ func generateNodeTemplate(memory string, cpu uint32, image string) *capikubevirt
Devices: kubevirtv1.Devices{
Disks: []kubevirtv1.Disk{
{
Name: "containervolume",
Name: "rhcos",
DiskDevice: kubevirtv1.DiskDevice{
Disk: &kubevirtv1.DiskTarget{
Bus: "virtio",
Expand All @@ -81,7 +81,7 @@ func generateNodeTemplate(memory string, cpu uint32, image string) *capikubevirt
},
Volumes: []kubevirtv1.Volume{
{
Name: "containervolume",
Name: "rhcos",
VolumeSource: kubevirtv1.VolumeSource{
ContainerDisk: &kubevirtv1.ContainerDiskSource{
Image: image,
Expand Down
90 changes: 76 additions & 14 deletions hypershift-operator/controllers/nodepool/nodepool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ type NodePoolReconciler struct {
upsert.CreateOrUpdateProvider
}

type awsRHCOSDetails struct {
AMI string
}

type kubevirtRHCOSDetails struct {
Checksum string
OpenStackDownloadURL string
}

type rhcosDetails struct {
AWS awsRHCOSDetails
KubeVirt kubevirtRHCOSDetails
}

func (r *NodePoolReconciler) SetupWithManager(mgr ctrl.Manager) error {
_, err := ctrl.NewControllerManagedBy(mgr).
For(&hyperv1.NodePool{}).
Expand Down Expand Up @@ -324,13 +338,14 @@ func (r *NodePoolReconciler) reconcile(ctx context.Context, hcluster *hyperv1.Ho
})

// Validate platform specific input.
var ami string
if nodePool.Spec.Platform.Type == hyperv1.AWSPlatform {
rhcosInfo := &rhcosDetails{}
switch nodePool.Spec.Platform.Type {
case hyperv1.AWSPlatform:
if hcluster.Spec.Platform.AWS == nil {
return ctrl.Result{}, fmt.Errorf("the HostedCluster for this NodePool has no .Spec.Platform.AWS, this is unsupported")
}
// TODO: Should the region be included in the NodePool platform information?
ami, err = getAMI(nodePool, hcluster.Spec.Platform.AWS.Region, releaseImage)
ami, err := getAMI(nodePool, hcluster.Spec.Platform.AWS.Region, releaseImage)
if err != nil {
setStatusCondition(&nodePool.Status.Conditions, hyperv1.NodePoolCondition{
Type: hyperv1.NodePoolValidAMIConditionType,
Expand All @@ -341,14 +356,35 @@ func (r *NodePoolReconciler) reconcile(ctx context.Context, hcluster *hyperv1.Ho
})
return ctrl.Result{}, fmt.Errorf("couldn't discover an AMI for release image: %w", err)
}
setStatusCondition(&nodePool.Status.Conditions, hyperv1.NodePoolCondition{
Type: hyperv1.NodePoolValidAMIConditionType,
Status: corev1.ConditionTrue,
Reason: hyperv1.NodePoolAsExpectedConditionReason,
Message: fmt.Sprintf("Bootstrap AMI is %q", ami),
ObservedGeneration: nodePool.Generation,
})
rhcosInfo.AWS = awsRHCOSDetails{AMI: ami}
case hyperv1.KubevirtPlatform:
release, shasum, location, err := getOpenStackDiskInfo(releaseImage)
if err != nil {
setStatusCondition(&nodePool.Status.Conditions, hyperv1.NodePoolCondition{
Type: hyperv1.NodePoolValidRHCOSImageConditionType,
Status: corev1.ConditionFalse,
Reason: hyperv1.NodePoolValidationFailedConditionReason,
Message: fmt.Sprintf("Couldn't discover any openstack rhcos checksum for release image %q: %s", nodePool.Spec.Release.Image, err.Error()),
ObservedGeneration: nodePool.Generation,
})
return ctrl.Result{}, fmt.Errorf("couldn't discover any openstack rhcos checksum for release image: %w", err)
}
setStatusCondition(&nodePool.Status.Conditions, hyperv1.NodePoolCondition{
Type: hyperv1.NodePoolValidRHCOSImageConditionType,
Status: corev1.ConditionTrue,
Reason: hyperv1.NodePoolAsExpectedConditionReason,
Message: fmt.Sprintf("Bootstrap openstack RHCOS image is release %q", release),
ObservedGeneration: nodePool.Generation,
})
rhcosInfo.KubeVirt = kubevirtRHCOSDetails{Checksum: shasum, OpenStackDownloadURL: location}
}
setStatusCondition(&nodePool.Status.Conditions, hyperv1.NodePoolCondition{
Type: hyperv1.NodePoolValidAMIConditionType,
Status: corev1.ConditionTrue,
Reason: hyperv1.NodePoolAsExpectedConditionReason,
Message: fmt.Sprintf("Bootstrap AMI is %q", ami),
ObservedGeneration: nodePool.Generation,
})

// Validate config input.
// 3 generic core config resoures: fips, ssh and haproxy.
Expand Down Expand Up @@ -501,7 +537,7 @@ func (r *NodePoolReconciler) reconcile(ctx context.Context, hcluster *hyperv1.Ho
}

// Reconcile (Platform)MachineTemplate.
template, mutateTemplate, machineTemplateSpecJSON, err := machineTemplateBuilders(hcluster, nodePool, infraID, ami)
template, mutateTemplate, machineTemplateSpecJSON, err := machineTemplateBuilders(hcluster, nodePool, infraID, rhcosInfo)
if err != nil {
return ctrl.Result{}, err
}
Expand Down Expand Up @@ -912,6 +948,10 @@ func getAMI(nodePool *hyperv1.NodePool, region string, releaseImage *releaseinfo
return defaultNodePoolAMI(region, releaseImage)
}

func getOpenStackDiskInfo(releaseImage *releaseinfo.ReleaseImage) (string, string, string, error) {
return defaultOpenStackInfo(releaseImage)
}

func ignConfig(encodedCACert, encodedToken, endpoint string) ignitionapi.Config {
return ignitionapi.Config{
Ignition: ignitionapi.Ignition{
Expand Down Expand Up @@ -1095,6 +1135,28 @@ func validateAutoscaling(nodePool *hyperv1.NodePool) error {
return nil
}

func defaultOpenStackInfo(releaseImage *releaseinfo.ReleaseImage) (release string, sha256 string, url string, err error) {
// TODO: The architecture should be specified from the API
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 artifact %v", "qcow2.gz")
}
disk, exists := artifact["disk"]
if !exists {
return "", "", "", fmt.Errorf("couldn't find OS metadata for the openstack disk")
}

return openStack.Release, disk.SHA256, disk.Location, nil
}

func defaultNodePoolAMI(region string, releaseImage *releaseinfo.ReleaseImage) (string, error) {
// TODO: The architecture should be specified from the API
arch, foundArch := releaseImage.StreamMetadata.Architectures["x86_64"]
Expand Down Expand Up @@ -1375,7 +1437,7 @@ func isPlatformNone(nodePool *hyperv1.NodePool) bool {
// a func to mutate the (platform)MachineTemplate.spec, a json string representation for (platform)MachineTemplate.spec
// and an error.
func machineTemplateBuilders(hcluster *hyperv1.HostedCluster, nodePool *hyperv1.NodePool,
infraID, ami string) (client.Object, func(object client.Object) error, string, error) {
infraID string, rhcosInfo *rhcosDetails) (client.Object, func(object client.Object) error, string, error) {
var mutateTemplate func(object client.Object) error
var template client.Object
var machineTemplateSpec interface{}
Expand All @@ -1384,7 +1446,7 @@ func machineTemplateBuilders(hcluster *hyperv1.HostedCluster, nodePool *hyperv1.
// Define the desired template type and mutateTemplate function.
case hyperv1.AWSPlatform:
template = &capiaws.AWSMachineTemplate{}
machineTemplateSpec = awsMachineTemplateSpec(infraID, ami, hcluster, nodePool)
machineTemplateSpec = awsMachineTemplateSpec(infraID, rhcosInfo.AWS.AMI, hcluster, nodePool)
mutateTemplate = func(object client.Object) error {
o, _ := object.(*capiaws.AWSMachineTemplate)
o.Spec = *machineTemplateSpec.(*capiaws.AWSMachineTemplateSpec)
Expand All @@ -1408,7 +1470,7 @@ func machineTemplateBuilders(hcluster *hyperv1.HostedCluster, nodePool *hyperv1.
}
case hyperv1.KubevirtPlatform:
template = &capikubevirt.KubevirtMachineTemplate{}
machineTemplateSpec = kubevirtMachineTemplateSpec(nodePool)
machineTemplateSpec = kubevirtMachineTemplateSpec(nodePool, rhcosInfo.KubeVirt)
mutateTemplate = func(object client.Object) error {
o, _ := object.(*capikubevirt.KubevirtMachineTemplate)
o.Spec = *machineTemplateSpec.(*capikubevirt.KubevirtMachineTemplateSpec)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ func RunTestMachineTemplateBuilders(t *testing.T, preCreateMachineTemplate bool)
expectedMachineTemplateSpecJSON, err := json.Marshal(expectedMachineTemplate.Spec)
g.Expect(err).ToNot(HaveOccurred())

template, mutateTemplate, machineTemplateSpecJSON, err := machineTemplateBuilders(hcluster, nodePool, infraID, ami)
template, mutateTemplate, machineTemplateSpecJSON, err := machineTemplateBuilders(hcluster, nodePool, infraID, &rhcosDetails{AWS: awsRHCOSDetails{AMI: ami}})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(machineTemplateSpecJSON).To(BeIdenticalTo(string(expectedMachineTemplateSpecJSON)))

Expand Down

0 comments on commit bd2113f

Please sign in to comment.