Skip to content

Commit

Permalink
test/e2e: Implement ClusterCLass rollout e2e test
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Büringer [email protected]
  • Loading branch information
sbueringer committed Mar 7, 2023
1 parent ca71ea4 commit f78c0bd
Show file tree
Hide file tree
Showing 15 changed files with 1,748 additions and 136 deletions.
51 changes: 51 additions & 0 deletions internal/contract/bootstrap_config_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package contract

import (
"sync"
)

// BootstrapConfigTemplateContract encodes information about the Machine API contract for BootstrapConfigTemplate objects
// like KubeadmConfigTemplate, etc.
type BootstrapConfigTemplateContract struct{}

var bootstrapConfigTemplate *BootstrapConfigTemplateContract
var onceBootstrapConfigTemplate sync.Once

// BootstrapConfigTemplate provide access to the information about the Machine API contract for BootstrapConfigTemplate objects.
func BootstrapConfigTemplate() *BootstrapConfigTemplateContract {
onceBootstrapConfigTemplate.Do(func() {
bootstrapConfigTemplate = &BootstrapConfigTemplateContract{}
})
return bootstrapConfigTemplate
}

// Template provides access to the template.
func (c *BootstrapConfigTemplateContract) Template() *BootstrapConfigTemplateTemplate {
return &BootstrapConfigTemplateTemplate{}
}

// BootstrapConfigTemplateTemplate provides a helper struct for working with the template in an BootstrapConfigTemplate.
type BootstrapConfigTemplateTemplate struct{}

// Metadata provides access to the metadata of a template.
func (c *BootstrapConfigTemplateTemplate) Metadata() *Metadata {
return &Metadata{
path: Path{"spec", "template", "metadata"},
}
}
51 changes: 51 additions & 0 deletions internal/contract/controlplane_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,54 @@ func (c *ControlPlaneTemplateContract) InfrastructureMachineTemplate() *Ref {
path: Path{"spec", "template", "spec", "machineTemplate", "infrastructureRef"},
}
}

// Template provides access to the template.
func (c *ControlPlaneTemplateContract) Template() *ControlPlaneTemplateTemplate {
return &ControlPlaneTemplateTemplate{}
}

// ControlPlaneTemplateTemplate provides a helper struct for working with the template in an ControlPlaneTemplate..
type ControlPlaneTemplateTemplate struct{}

// Metadata provides access to the metadata of a template.
func (c *ControlPlaneTemplateTemplate) Metadata() *Metadata {
return &Metadata{
path: Path{"spec", "template", "metadata"},
}
}

// MachineTemplate provides access to MachineTemplate in a ControlPlaneTemplate object, if any.
func (c *ControlPlaneTemplateTemplate) MachineTemplate() *ControlPlaneTemplateMachineTemplate {
return &ControlPlaneTemplateMachineTemplate{}
}

// ControlPlaneTemplateMachineTemplate provides a helper struct for working with MachineTemplate.
type ControlPlaneTemplateMachineTemplate struct{}

// Metadata provides access to the metadata of the MachineTemplate of a ControlPlaneTemplate.
func (c *ControlPlaneTemplateMachineTemplate) Metadata() *Metadata {
return &Metadata{
path: Path{"spec", "template", "spec", "machineTemplate", "metadata"},
}
}

// NodeDrainTimeout provides access to the nodeDrainTimeout of a MachineTemplate.
func (c *ControlPlaneTemplateMachineTemplate) NodeDrainTimeout() *Duration {
return &Duration{
path: Path{"spec", "template", "spec", "machineTemplate", "nodeDrainTimeout"},
}
}

// NodeVolumeDetachTimeout provides access to the nodeVolumeDetachTimeout of a MachineTemplate.
func (c *ControlPlaneTemplateMachineTemplate) NodeVolumeDetachTimeout() *Duration {
return &Duration{
path: Path{"spec", "template", "spec", "machineTemplate", "nodeVolumeDetachTimeout"},
}
}

// NodeDeletionTimeout provides access to the nodeDeletionTimeout of a MachineTemplate.
func (c *ControlPlaneTemplateMachineTemplate) NodeDeletionTimeout() *Duration {
return &Duration{
path: Path{"spec", "template", "spec", "machineTemplate", "nodeDeletionTimeout"},
}
}
51 changes: 51 additions & 0 deletions internal/contract/infrastructure_cluster_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package contract

import (
"sync"
)

// InfrastructureClusterTemplateContract encodes information about the Cluster API contract for InfrastructureClusterTemplate objects
// like DockerClusterTemplates, AWSClusterTemplates, etc.
type InfrastructureClusterTemplateContract struct{}

var infrastructureClusterTemplate *InfrastructureClusterTemplateContract
var onceInfrastructureClusterTemplate sync.Once

// InfrastructureClusterTemplate provides access to the information about the Cluster API contract for InfrastructureClusterTemplate objects.
func InfrastructureClusterTemplate() *InfrastructureClusterTemplateContract {
onceInfrastructureClusterTemplate.Do(func() {
infrastructureClusterTemplate = &InfrastructureClusterTemplateContract{}
})
return infrastructureClusterTemplate
}

// Template provides access to the template.
func (c *InfrastructureClusterTemplateContract) Template() *InfrastructureClusterTemplateTemplate {
return &InfrastructureClusterTemplateTemplate{}
}

// InfrastructureClusterTemplateTemplate provides a helper struct for working with the template in an InfrastructureClusterTemplate..
type InfrastructureClusterTemplateTemplate struct{}

// Metadata provides access to the metadata of a template.
func (c *InfrastructureClusterTemplateTemplate) Metadata() *Metadata {
return &Metadata{
path: Path{"spec", "template", "metadata"},
}
}
51 changes: 51 additions & 0 deletions internal/contract/infrastructure_machine_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package contract

import (
"sync"
)

// InfrastructureMachineTemplateContract encodes information about the Machine API contract for InfrastructureMachineTemplate objects
// like DockerMachineTemplates, AWSMachineTemplates, etc.
type InfrastructureMachineTemplateContract struct{}

var infrastructureMachineTemplate *InfrastructureMachineTemplateContract
var onceInfrastructureMachineTemplate sync.Once

// InfrastructureMachineTemplate provide access to the information about the Machine API contract for InfrastructureMachineTemplate objects.
func InfrastructureMachineTemplate() *InfrastructureMachineTemplateContract {
onceInfrastructureMachineTemplate.Do(func() {
infrastructureMachineTemplate = &InfrastructureMachineTemplateContract{}
})
return infrastructureMachineTemplate
}

// Template provides access to the template.
func (c *InfrastructureMachineTemplateContract) Template() *InfrastructureMachineTemplateTemplate {
return &InfrastructureMachineTemplateTemplate{}
}

// InfrastructureMachineTemplateTemplate provides a helper struct for working with the template in an InfrastructureMachineTemplate.
type InfrastructureMachineTemplateTemplate struct{}

// Metadata provides access to the metadata of a template.
func (c *InfrastructureMachineTemplateTemplate) Metadata() *Metadata {
return &Metadata{
path: Path{"spec", "template", "metadata"},
}
}
32 changes: 21 additions & 11 deletions internal/controllers/topology/cluster/desired_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,22 @@ func (r *Reconciler) computeControlPlane(ctx context.Context, s *scope.Scope, in
return nil, errors.Wrap(err, "failed to spec.machineTemplate.infrastructureRef in the ControlPlane object")
}

// Apply the ControlPlane labels and annotations to the ControlPlane machines as well.
// Add the ControlPlane labels and annotations to the ControlPlane machines as well.
// Note: We have to ensure the machine template metadata copied from the control plane template is not overwritten.
controlPlaneMachineTemplateMetadata, err := contract.ControlPlane().MachineTemplate().Metadata().Get(controlPlane)
if err != nil {
return nil, errors.Wrap(err, "failed to get spec.machineTemplate.metadata from the ControlPlane object")
}
for k, v := range controlPlaneLabels {
controlPlaneMachineTemplateMetadata.Labels[k] = v
}
for k, v := range controlPlaneAnnotations {
controlPlaneMachineTemplateMetadata.Annotations[k] = v
}
if err := contract.ControlPlane().MachineTemplate().Metadata().Set(controlPlane,
&clusterv1.ObjectMeta{
Labels: controlPlaneLabels,
Annotations: controlPlaneAnnotations,
Labels: controlPlaneMachineTemplateMetadata.Labels,
Annotations: controlPlaneMachineTemplateMetadata.Annotations,
}); err != nil {
return nil, errors.Wrap(err, "failed to set spec.machineTemplate.metadata in the ControlPlane object")
}
Expand Down Expand Up @@ -990,15 +1001,14 @@ func templateToTemplate(in templateToInput) *unstructured.Unstructured {
return template
}

// mergeMap merges two maps into another one.
// NOTE: In case a key exists in both maps, the value in the first map is preserved.
func mergeMap(a, b map[string]string) map[string]string {
// mergeMap merges maps.
// NOTE: In case a key exists in multiple maps, the value of the first map is preserved.
func mergeMap(maps ...map[string]string) map[string]string {
m := make(map[string]string)
for k, v := range b {
m[k] = v
}
for k, v := range a {
m[k] = v
for i := len(maps) - 1; i >= 0; i-- {
for k, v := range maps[i] {
m[k] = v
}
}

// Nil the result if the map is empty, thus avoiding triggering infinite reconcile
Expand Down
28 changes: 23 additions & 5 deletions internal/controllers/topology/cluster/desired_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,17 @@ func TestComputeControlPlane(t *testing.T) {

controlPlaneTemplate := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "template1").
Build()
controlPlaneMachineTemplateLabels := map[string]string{
"machineTemplateLabel": "machineTemplateLabelValue",
}
controlPlaneMachineTemplateAnnotations := map[string]string{
"machineTemplateAnnotation": "machineTemplateAnnotationValue",
}
controlPlaneTemplateWithMachineTemplate := controlPlaneTemplate.DeepCopy()
_ = contract.ControlPlaneTemplate().Template().MachineTemplate().Metadata().Set(controlPlaneTemplateWithMachineTemplate, &clusterv1.ObjectMeta{
Labels: controlPlaneMachineTemplateLabels,
Annotations: controlPlaneMachineTemplateAnnotations,
})
clusterClassDuration := 20 * time.Second
clusterClass := builder.ClusterClass(metav1.NamespaceDefault, "class1").
WithControlPlaneMetadata(labels, annotations).
Expand Down Expand Up @@ -423,15 +434,15 @@ func TestComputeControlPlane(t *testing.T) {
infrastructureMachineTemplate := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "template1").Build()
clusterClass := builder.ClusterClass(metav1.NamespaceDefault, "class1").
WithControlPlaneMetadata(labels, annotations).
WithControlPlaneTemplate(controlPlaneTemplate).
WithControlPlaneTemplate(controlPlaneTemplateWithMachineTemplate).
WithControlPlaneInfrastructureMachineTemplate(infrastructureMachineTemplate).Build()

// aggregating templates and cluster class into a blueprint (simulating getBlueprint)
blueprint := &scope.ClusterBlueprint{
Topology: cluster.Spec.Topology,
ClusterClass: clusterClass,
ControlPlane: &scope.ControlPlaneBlueprint{
Template: controlPlaneTemplate,
Template: controlPlaneTemplateWithMachineTemplate,
InfrastructureMachineTemplate: infrastructureMachineTemplate,
},
}
Expand All @@ -447,10 +458,17 @@ func TestComputeControlPlane(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(obj).ToNot(BeNil())

// machineTemplate is removed from the template for assertion as we can't
// simply compare the machineTemplate in template with the one in object as
// computeControlPlane() adds additional fields like the timeouts to machineTemplate.
// Note: machineTemplate ia asserted further down below instead.
controlPlaneTemplateWithoutMachineTemplate := blueprint.ControlPlane.Template.DeepCopy()
unstructured.RemoveNestedField(controlPlaneTemplateWithoutMachineTemplate.Object, "spec", "template", "spec", "machineTemplate")

assertTemplateToObject(g, assertTemplateInput{
cluster: s.Current.Cluster,
templateRef: blueprint.ClusterClass.Spec.ControlPlane.Ref,
template: blueprint.ControlPlane.Template,
template: controlPlaneTemplateWithoutMachineTemplate,
currentRef: nil,
obj: obj,
labels: mergeMap(blueprint.Topology.ControlPlane.Metadata.Labels, blueprint.ClusterClass.Spec.ControlPlane.Metadata.Labels),
Expand All @@ -459,12 +477,12 @@ func TestComputeControlPlane(t *testing.T) {
gotMetadata, err := contract.ControlPlane().MachineTemplate().Metadata().Get(obj)
g.Expect(err).ToNot(HaveOccurred())

expectedLabels := mergeMap(s.Current.Cluster.Spec.Topology.ControlPlane.Metadata.Labels, blueprint.ClusterClass.Spec.ControlPlane.Metadata.Labels)
expectedLabels := mergeMap(s.Current.Cluster.Spec.Topology.ControlPlane.Metadata.Labels, blueprint.ClusterClass.Spec.ControlPlane.Metadata.Labels, controlPlaneMachineTemplateLabels)
expectedLabels[clusterv1.ClusterNameLabel] = cluster.Name
expectedLabels[clusterv1.ClusterTopologyOwnedLabel] = ""
g.Expect(gotMetadata).To(Equal(&clusterv1.ObjectMeta{
Labels: expectedLabels,
Annotations: mergeMap(s.Current.Cluster.Spec.Topology.ControlPlane.Metadata.Annotations, blueprint.ClusterClass.Spec.ControlPlane.Metadata.Annotations),
Annotations: mergeMap(s.Current.Cluster.Spec.Topology.ControlPlane.Metadata.Annotations, blueprint.ClusterClass.Spec.ControlPlane.Metadata.Annotations, controlPlaneMachineTemplateAnnotations),
}))

assertNestedField(g, obj, version, contract.ControlPlane().Version().Path()...)
Expand Down
Loading

0 comments on commit f78c0bd

Please sign in to comment.