Skip to content

Commit

Permalink
Fetch latest E2E test AMI ID dynamically
Browse files Browse the repository at this point in the history
  • Loading branch information
abhay-krishna committed Jun 3, 2024
1 parent 8e85e1e commit c27d8d3
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 8 deletions.
1 change: 0 additions & 1 deletion cmd/integration_test/build/script/create_infra_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ cat << EOF > ${INTEGRATION_TEST_INFRA_CONFIG}
---
ec2:
amiId: ${INTEGRATION_TEST_AL2_AMI_ID}
subnetId: ${INTEGRATION_TEST_SUBNET_ID}
vSphere:
Expand Down
81 changes: 77 additions & 4 deletions internal/pkg/ec2/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"
"fmt"
"math/rand"
"reflect"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -15,6 +16,13 @@ import (
"github.com/aws/eks-anywhere/pkg/retrier"
)

const (
e2eTestAMINameFilter = "eksa-integration-test-AL2-*"
e2eTestAMIDescriptionFilter = "*Kernel version 5.X*"
e2eTestAMIOwner = "857151390494"
timeLayout = time.RFC3339
)

var dockerLogsUserData = `
#!/bin/bash
cat <<'EOF' >> /etc/docker/daemon.json
Expand All @@ -26,7 +34,10 @@ EOF
systemctl restart docker --no-block
`

func CreateInstance(session *session.Session, amiId, key, tag, instanceProfileName, subnetId, name string) (string, error) {
// CreateInstance launches a test runner EC2 instance in the given subnet and
// with the given properties. The AMI ID used for the instance is dynamically
// obtained.
func CreateInstance(session *session.Session, key, tag, instanceProfileName, subnetID, name string) (string, error) {

Check warning on line 40 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L40

Added line #L40 was not covered by tests
r := retrier.New(180*time.Minute, retrier.WithBackoffFactor(1.5), retrier.WithRetryPolicy(func(totalRetries int, err error) (retry bool, wait time.Duration) {
// EC2 Request token bucket has a refill rate of 2 request tokens
// per second, so waiting between 5 and 10 seconds per retry with a backoff factor of 1.5 should be sufficient
Expand All @@ -41,12 +52,47 @@ func CreateInstance(session *session.Session, amiId, key, tag, instanceProfileNa
}))

service := ec2.New(session)
var result *ec2.Reservation

var describeImagesResult *ec2.DescribeImagesOutput

Check warning on line 56 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L56

Added line #L56 was not covered by tests
err := r.Retry(func() error {
var err error
describeImagesResult, err = service.DescribeImages(&ec2.DescribeImagesInput{
Filters: []*ec2.Filter{

Check warning on line 60 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L58-L60

Added lines #L58 - L60 were not covered by tests
{
Name: aws.String("name"),
Values: []*string{
aws.String(e2eTestAMINameFilter),
},
},

Check warning on line 66 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L62-L66

Added lines #L62 - L66 were not covered by tests
{
Name: aws.String("description"),
Values: []*string{
aws.String(e2eTestAMIDescriptionFilter),
},
},
},
Owners: []*string{
aws.String(e2eTestAMIOwner),
},
})

Check warning on line 77 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L68-L77

Added lines #L68 - L77 were not covered by tests

return err
})
if err != nil {
return "", fmt.Errorf("retries exhausted when trying to describe images: %v", err)

Check warning on line 82 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L79-L82

Added lines #L79 - L82 were not covered by tests
}

amiID, err := getLatestAMIID(describeImagesResult.Images)
if err != nil {
return "", fmt.Errorf("getting latest AMI ID by creation time: %v", err)

Check warning on line 87 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L85-L87

Added lines #L85 - L87 were not covered by tests
}
fmt.Printf("Found latest E2E test AMI: %v\n", amiID)

Check warning on line 89 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L89

Added line #L89 was not covered by tests

var result *ec2.Reservation
err = r.Retry(func() error {

Check warning on line 92 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L91-L92

Added lines #L91 - L92 were not covered by tests
var err error
result, err = service.RunInstances(&ec2.RunInstancesInput{
ImageId: aws.String(amiId),
ImageId: aws.String(amiID),

Check warning on line 95 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L95

Added line #L95 was not covered by tests
InstanceType: aws.String("t3.2xlarge"),
MinCount: aws.Int64(1),
MaxCount: aws.Int64(1),
Expand All @@ -61,7 +107,7 @@ func CreateInstance(session *session.Session, amiId, key, tag, instanceProfileNa
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
Name: aws.String(instanceProfileName),
},
SubnetId: aws.String(subnetId),
SubnetId: aws.String(subnetID),

Check warning on line 110 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L110

Added line #L110 was not covered by tests
TagSpecifications: []*ec2.TagSpecification{
{
ResourceType: aws.String("instance"),
Expand Down Expand Up @@ -111,3 +157,30 @@ func isThrottleError(err error) bool {

return false
}

func getLatestAMIID(images []*ec2.Image) (string, error) {
latestImage := &ec2.Image{}
latestImage.CreationDate = aws.String(time.Time{}.Format(timeLayout))
for _, image := range images {
if image.CreationDate != nil {
imageCreationTime, err := time.Parse(timeLayout, *image.CreationDate)
if err != nil {
return "", fmt.Errorf("parsing creation timestamp for image: %v", err)
}

latestImageCreationTime, err := time.Parse(timeLayout, *latestImage.CreationDate)
if err != nil {
return "", fmt.Errorf("parsing creation timestamp for current latest image: %v", err)

Check warning on line 173 in internal/pkg/ec2/create.go

View check run for this annotation

Codecov / codecov/patch

internal/pkg/ec2/create.go#L173

Added line #L173 was not covered by tests
}
if latestImageCreationTime.Before(imageCreationTime) {
latestImage = image
}
}
}

if reflect.DeepEqual(latestImage, &ec2.Image{CreationDate: aws.String(time.Time{}.Format(timeLayout))}) {
return "", fmt.Errorf("no matching AMIs found")
}

return *latestImage.ImageId, nil
}
72 changes: 72 additions & 0 deletions internal/pkg/ec2/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ec2

import (
"testing"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)

const incorrectTimeLayout = time.RFC1123

func TestGetLatestAMIID(t *testing.T) {
tests := []struct {
name string
images []*ec2.Image
wantAMIID string
wantErr bool
}{
{
name: "Get latest image ID success",
images: []*ec2.Image{
{
ImageId: aws.String("ami-01234"),
CreationDate: aws.String(time.Now().Format(timeLayout)),
},
{
ImageId: aws.String("ami-56789"),
CreationDate: aws.String(time.Time{}.Format(timeLayout)),
},
},
wantAMIID: "ami-01234",
wantErr: false,
},
{
name: "Get latest image ID failure - incorrect time layout",
images: []*ec2.Image{
{
ImageId: aws.String("ami-01234"),
CreationDate: aws.String(time.Now().Format(timeLayout)),
},
{
ImageId: aws.String("ami-56789"),
CreationDate: aws.String(time.Time{}.Format(incorrectTimeLayout)),
},
},
wantAMIID: "",
wantErr: true,
},
{
name: "Get latest image ID failure - empty images list",
images: []*ec2.Image{},
wantAMIID: "",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotImageID, err := getLatestAMIID(tt.images)
if (err != nil) != tt.wantErr {
t.Errorf("getLatestAMIID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil {
if gotImageID != tt.wantAMIID {
t.Errorf("getLatestAMIID() got = %v, want %v", gotImageID, tt.wantAMIID)
}
}
})
}
}
3 changes: 1 addition & 2 deletions internal/test/e2e/testRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ type testRunner struct {

type Ec2TestRunner struct {
testRunner
AmiID string `yaml:"amiId"`
SubnetID string `yaml:"subnetId"`
}

Expand Down Expand Up @@ -187,7 +186,7 @@ func (v *VSphereTestRunner) createInstance(c instanceRunConf) (string, error) {
func (e *Ec2TestRunner) createInstance(c instanceRunConf) (string, error) {
name := getTestRunnerName(e.logger, c.JobID)
e.logger.V(1).Info("Creating ec2 Test Runner instance", "name", name)
instanceID, err := ec2.CreateInstance(c.Session, e.AmiID, key, tag, c.InstanceProfileName, e.SubnetID, name)
instanceID, err := ec2.CreateInstance(c.Session, key, tag, c.InstanceProfileName, e.SubnetID, name)
if err != nil {
return "", fmt.Errorf("creating instance for e2e tests: %v", err)
}
Expand Down
1 change: 0 additions & 1 deletion scripts/e2e_test_docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ cat << EOF > ${INTEGRATION_TEST_INFRA_CONFIG}
---
ec2:
amiId: ${INTEGRATION_TEST_AL2_AMI_ID}
subnetId:
EOF
Expand Down

0 comments on commit c27d8d3

Please sign in to comment.