Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions e2e/aws_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package e2e

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
"github.com/openshift/cluster-capi-operator/e2e/framework"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
yaml "sigs.k8s.io/yaml"
)

const (
awsMachineTemplateName = "aws-machine-template"
machineSetOpenshiftLabelKey = "machine.openshift.io/cluster-api-machineset"
)

// getDefaultAWSMAPIProviderSpec retrieves the first MAPI MachineSet and extracts its AWS providerSpec for use as a template.
func getDefaultAWSMAPIProviderSpec() (*mapiv1beta1.MachineSet, *mapiv1beta1.AWSMachineProviderConfig) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sunzhaohua2 shall we add comments to helper functions , so that they can be available on IDEs and godocs?

machineSetList := &mapiv1beta1.MachineSetList{}
Eventually(komega.List(machineSetList, client.InNamespace(framework.MAPINamespace))).Should(Succeed(), "Failed to list MachineSets in MAPI namespace")

Expect(machineSetList.Items).ToNot(BeNil())
Expect(machineSetList.Items).ToNot(HaveLen(0), "No MachineSets found in namespace %s", framework.MAPINamespace)
machineSet := &machineSetList.Items[0]
Expect(machineSet.Spec.Template.Spec.ProviderSpec.Value).ToNot(BeNil())

providerSpec := &mapiv1beta1.AWSMachineProviderConfig{}
Expect(yaml.Unmarshal(machineSet.Spec.Template.Spec.ProviderSpec.Value.Raw, providerSpec)).To(Succeed())

return machineSet, providerSpec
}

// newAWSMachineTemplate creates an AWSMachineTemplate from a MAPI AWS providerSpec by converting relevant fields.
func newAWSMachineTemplate(mapiProviderSpec *mapiv1beta1.AWSMachineProviderConfig) *awsv1.AWSMachineTemplate {
By("Creating AWS machine template")

Expect(mapiProviderSpec).ToNot(BeNil())
Expect(mapiProviderSpec.IAMInstanceProfile).ToNot(BeNil())
Expect(mapiProviderSpec.IAMInstanceProfile.ID).ToNot(BeNil())
Expect(mapiProviderSpec.InstanceType).ToNot(BeEmpty())
Expect(mapiProviderSpec.Placement.AvailabilityZone).ToNot(BeEmpty())
Expect(mapiProviderSpec.AMI.ID).ToNot(BeNil())
Expect(mapiProviderSpec.Subnet.Filters).ToNot(HaveLen(0))
Expect(mapiProviderSpec.Subnet.Filters[0].Values).ToNot(HaveLen(0))
Expect(mapiProviderSpec.SecurityGroups).ToNot(HaveLen(0))
Expect(mapiProviderSpec.SecurityGroups[0].Filters).ToNot(HaveLen(0))
Expect(mapiProviderSpec.SecurityGroups[0].Filters[0].Values).ToNot(HaveLen(0))

awsMachineSpec := awsv1.AWSMachineSpec{
IAMInstanceProfile: *mapiProviderSpec.IAMInstanceProfile.ID,
InstanceType: mapiProviderSpec.InstanceType,
AMI: awsv1.AMIReference{
ID: mapiProviderSpec.AMI.ID,
},
Ignition: &awsv1.Ignition{
StorageType: awsv1.IgnitionStorageTypeOptionUnencryptedUserData,
},
Subnet: &awsv1.AWSResourceReference{
Filters: []awsv1.Filter{
{
Name: "tag:Name",
Values: mapiProviderSpec.Subnet.Filters[0].Values,
},
},
},
AdditionalSecurityGroups: []awsv1.AWSResourceReference{
{
Filters: []awsv1.Filter{
{
Name: "tag:Name",
Values: mapiProviderSpec.SecurityGroups[0].Filters[0].Values,
},
},
},
},
}

awsMachineTemplate := &awsv1.AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: awsMachineTemplateName,
Namespace: framework.CAPINamespace,
},
Spec: awsv1.AWSMachineTemplateSpec{
Template: awsv1.AWSMachineTemplateResource{
Spec: awsMachineSpec,
},
},
}

return awsMachineTemplate
}

// createAWSClient creates an AWS EC2 client using credentials from the CAPI bootstrap credentials secret.
func createAWSClient(region string) *ec2.EC2 {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "capa-manager-bootstrap-credentials",
Namespace: framework.CAPINamespace,
},
}
Eventually(komega.Get(secret)).Should(Succeed(), "Failed to get AWS credentials secret")

accessKey := secret.Data["aws_access_key_id"]
Expect(accessKey).ToNot(BeNil())
secretAccessKey := secret.Data["aws_secret_access_key"]
Expect(secretAccessKey).ToNot(BeNil())

awsConfig := &aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(
string(accessKey),
string(secretAccessKey),
"",
),
}

sess, err := session.NewSession(awsConfig)
Expect(err).ToNot(HaveOccurred())

return ec2.New(sess)
}

// getMAPICreatedInstance retrieves the EC2 instance created by a MAPI MachineSet using the AWS API.
func getMAPICreatedInstance(awsClient *ec2.EC2, msName string) ec2.Instance {
Expect(awsClient).ToNot(BeNil())
Expect(msName).ToNot(BeEmpty())
mapiMachineList := &mapiv1beta1.MachineList{}
Eventually(komega.ObjectList(mapiMachineList, client.InNamespace(framework.MAPINamespace), client.MatchingLabels{
machineSetOpenshiftLabelKey: msName,
})).Should(HaveField("Items", Not(BeEmpty())), "Failed to find MAPI machines for MachineSet %s", msName)

mapiMachine := mapiMachineList.Items[0]
Expect(mapiMachine.Status.ProviderStatus).ToNot(BeNil())

mapiProviderStatus := &mapiv1beta1.AWSMachineProviderStatus{}
Expect(yaml.Unmarshal(mapiMachine.Status.ProviderStatus.Raw, mapiProviderStatus)).To(Succeed())

Expect(mapiProviderStatus.InstanceID).ToNot(BeNil())
Expect(*mapiProviderStatus.InstanceID).ToNot(BeEmpty())

request := &ec2.DescribeInstancesInput{
InstanceIds: aws.StringSlice([]string{*mapiProviderStatus.InstanceID}),
}

result, err := awsClient.DescribeInstances(request)
Expect(err).ToNot(HaveOccurred())

Expect(result.Reservations).To(HaveLen(1))
Expect(result.Reservations[0].Instances).To(HaveLen(1))

return *result.Reservations[0].Instances[0]
}

// getCAPICreatedInstance retrieves the EC2 instance created by a CAPI MachineSet using the AWS API.
func getCAPICreatedInstance(awsClient *ec2.EC2, msName string) ec2.Instance {
Expect(awsClient).ToNot(BeNil())
Expect(msName).ToNot(BeEmpty())
capiMachineList := &awsv1.AWSMachineList{}

Eventually(komega.ObjectList(capiMachineList, client.InNamespace(framework.CAPINamespace), client.MatchingLabels{
machineSetOpenshiftLabelKey: msName,
})).Should(HaveField("Items", HaveLen(1)), "Failed to find exactly one CAPI AWSMachine for MachineSet %s", msName)

capiMachine := capiMachineList.Items[0]
Expect(capiMachine.Spec.InstanceID).ToNot(BeNil(), "AWSMachine InstanceID not set in Spec")
Expect(*capiMachine.Spec.InstanceID).ToNot(BeEmpty(), "AWSMachine InstanceID is empty")

request := &ec2.DescribeInstancesInput{
InstanceIds: aws.StringSlice([]string{*capiMachine.Spec.InstanceID}),
}

result, err := awsClient.DescribeInstances(request)
Expect(err).ToNot(HaveOccurred())

Expect(result.Reservations).To(HaveLen(1))
Expect(result.Reservations[0].Instances).To(HaveLen(1))

return *result.Reservations[0].Instances[0]
}

// compareInstances compares EC2 instances created by MAPI and CAPI MachineSets, logging any differences while ignoring instance-specific fields.
func compareInstances(awsClient *ec2.EC2, mapiMsName, capiMsName string) {
By("Comparing instances created by MAPI and CAPI")
mapiEC2Instance := getMAPICreatedInstance(awsClient, mapiMsName)
capiEC2Instance := getCAPICreatedInstance(awsClient, capiMsName)

// Ignore fields that are unique for each instance
ignoreInstanceFields := cmpopts.IgnoreFields(ec2.Instance{},
"InstanceId",
"ClientToken",
"LaunchTime",
"PrivateDnsName",
"PrivateIpAddress",
"UsageOperationUpdateTime",
"Tags", // tags won't match we should write a set of tests for comparing them manually
)

ignoreNicFields := cmpopts.IgnoreFields(ec2.InstanceNetworkInterface{},
"MacAddress",
"NetworkInterfaceId",
"PrivateDnsName",
"PrivateIpAddress",
)
ignorePrivateIpFields := cmpopts.IgnoreFields(ec2.InstancePrivateIpAddress{},
"PrivateDnsName",
"PrivateIpAddress",
)
// Ignore variable fields on nested types explicitly
ignoreEbsFields := cmpopts.IgnoreFields(ec2.EbsInstanceBlockDevice{},
"AttachTime",
"VolumeId",
)
ignoreNicAttachmentFields := cmpopts.IgnoreFields(ec2.NetworkInterfaceAttachment{},
"AttachTime",
"AttachmentId",
)
// Tags won't match we should write a set of tests for comparing them manually
ignoreTags := cmpopts.IgnoreTypes(ec2.Tag{})
cmpOpts := []cmp.Option{
ignoreInstanceFields,
ignoreEbsFields,
ignoreNicFields,
ignoreNicAttachmentFields,
ignorePrivateIpFields,
ignoreTags,
cmpopts.EquateEmpty(),
}

if !cmp.Equal(mapiEC2Instance, capiEC2Instance, cmpOpts...) {
GinkgoWriter.Print("Instances created by MAPI and CAPI are not equal\n" + cmp.Diff(mapiEC2Instance, capiEC2Instance, cmpOpts...))
}
}
Loading