mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 10:52:04 +00:00
164 lines
6.6 KiB
Go
164 lines
6.6 KiB
Go
//go:build e2e
|
|
|
|
package rds
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/rds"
|
|
"github.com/mulgadc/spinifex/tests/e2e/harness"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestCreateDescribe drives the control-plane half of the first live RDS path:
|
|
// CreateDBInstance (postgres, single-AZ) → available → the describe and tag
|
|
// views a customer and the Terraform provider read it back through.
|
|
//
|
|
// The client leg is TestConnectivity's: a connection is only meaningful from
|
|
// inside the customer VPC. The instance is created once and shared by the
|
|
// subtests, because booting the VM and running initdb is by far the slowest step.
|
|
func TestCreateDescribe(t *testing.T) {
|
|
f := requireRDSFixture(t)
|
|
t.Parallel()
|
|
reserveDBVMs(t, dbClass)
|
|
|
|
id := fmt.Sprintf("%s-%d", dbInstancePfx, time.Now().Unix())
|
|
|
|
harness.Phase(t, "Creating DB instance %q", id)
|
|
created := createDBInstance(t, f, id, func(in *rds.CreateDBInstanceInput) {
|
|
in.Tags = []*rds.Tag{
|
|
{Key: aws.String("env"), Value: aws.String("e2e")},
|
|
{Key: aws.String("suite"), Value: aws.String("rds")},
|
|
}
|
|
})
|
|
|
|
// The create returns before the engine exists, so the customer sees creating
|
|
// and polls; the reconciler owns the flip to available.
|
|
assert.Equal(t, harness.DBInstanceCreating, aws.StringValue(created.DBInstanceStatus))
|
|
assert.Equal(t, id, aws.StringValue(created.DBInstanceIdentifier))
|
|
|
|
var instance *rds.DBInstance
|
|
t.Run("BecomesAvailable", func(t *testing.T) {
|
|
instance = waitForAvailable(t, f, id)
|
|
|
|
assert.Equal(t, dbEngine, aws.StringValue(instance.Engine))
|
|
assert.Equal(t, dbClass, aws.StringValue(instance.DBInstanceClass))
|
|
assert.Equal(t, int64(dbStorageGiB), aws.Int64Value(instance.AllocatedStorage))
|
|
assert.Equal(t, dbMasterUser, aws.StringValue(instance.MasterUsername))
|
|
assert.True(t, aws.BoolValue(instance.StorageEncrypted), "the data volume is encrypted with the cluster key")
|
|
assert.False(t, aws.BoolValue(instance.MultiAZ))
|
|
assert.False(t, aws.BoolValue(instance.PubliclyAccessible))
|
|
|
|
require.NotNil(t, instance.Endpoint, "an available instance must publish an endpoint")
|
|
assert.NotEmpty(t, aws.StringValue(instance.Endpoint.Address))
|
|
assert.Equal(t, int64(5432), aws.Int64Value(instance.Endpoint.Port))
|
|
})
|
|
|
|
t.Run("RecordsCreationAndAvailabilityEvents", func(t *testing.T) {
|
|
requireAvailable(t, instance)
|
|
out, err := f.AWS.RDS.DescribeEvents(&rds.DescribeEventsInput{
|
|
SourceType: aws.String("db-instance"),
|
|
SourceIdentifier: aws.String(id),
|
|
Duration: aws.Int64(1440),
|
|
})
|
|
require.NoError(t, err, "describe-events")
|
|
require.NotEmpty(t, out.Events, "a freshly created instance must have an event history")
|
|
|
|
eventsByMessage := make(map[string]*rds.Event, len(out.Events))
|
|
for _, event := range out.Events {
|
|
eventsByMessage[aws.StringValue(event.Message)] = event
|
|
}
|
|
createdEvent := eventsByMessage["DB instance created."]
|
|
require.NotNil(t, createdEvent)
|
|
assert.Contains(t, aws.StringValueSlice(createdEvent.EventCategories), "creation")
|
|
availableEvent := eventsByMessage["DB instance is available."]
|
|
require.NotNil(t, availableEvent)
|
|
assert.Contains(t, aws.StringValueSlice(availableEvent.EventCategories), "availability")
|
|
})
|
|
|
|
t.Run("AppearsInTheFleetListing", func(t *testing.T) {
|
|
requireAvailable(t, instance)
|
|
list, err := f.AWS.RDS.DescribeDBInstances(&rds.DescribeDBInstancesInput{})
|
|
require.NoError(t, err, "describe-db-instances")
|
|
|
|
ids := make([]string, 0, len(list.DBInstances))
|
|
for _, i := range list.DBInstances {
|
|
ids = append(ids, aws.StringValue(i.DBInstanceIdentifier))
|
|
}
|
|
assert.Contains(t, ids, id, "an unfiltered describe must list the instance")
|
|
})
|
|
|
|
// The Terraform provider reads tags through ListTagsForResource on every
|
|
// apply, so this is the assertion that proves the provider's read path works.
|
|
t.Run("TagsRoundTrip", func(t *testing.T) {
|
|
requireAvailable(t, instance)
|
|
arn := aws.StringValue(instance.DBInstanceArn)
|
|
require.NotEmpty(t, arn, "an available instance must publish its ARN")
|
|
|
|
tags, err := f.AWS.RDS.ListTagsForResource(&rds.ListTagsForResourceInput{ResourceName: aws.String(arn)})
|
|
require.NoError(t, err, "list-tags-for-resource")
|
|
assert.Equal(t, map[string]string{"env": "e2e", "suite": "rds"}, tagMap(tags.TagList),
|
|
"the tags supplied at create must be readable back")
|
|
|
|
_, err = f.AWS.RDS.AddTagsToResource(&rds.AddTagsToResourceInput{
|
|
ResourceName: aws.String(arn),
|
|
Tags: []*rds.Tag{{Key: aws.String("env"), Value: aws.String("e2e-updated")}},
|
|
})
|
|
require.NoError(t, err, "add-tags-to-resource")
|
|
|
|
_, err = f.AWS.RDS.RemoveTagsFromResource(&rds.RemoveTagsFromResourceInput{
|
|
ResourceName: aws.String(arn),
|
|
TagKeys: aws.StringSlice([]string{"suite", "never-set"}),
|
|
})
|
|
require.NoError(t, err, "remove-tags-from-resource must ignore a key that is not present")
|
|
|
|
tags, err = f.AWS.RDS.ListTagsForResource(&rds.ListTagsForResourceInput{ResourceName: aws.String(arn)})
|
|
require.NoError(t, err, "list-tags-for-resource")
|
|
assert.Equal(t, map[string]string{"env": "e2e-updated"}, tagMap(tags.TagList))
|
|
|
|
// Terraform reads tags from the describe as well, so the two views
|
|
// disagreeing would show up as permanent drift.
|
|
described, err := f.AWS.RDS.DescribeDBInstances(&rds.DescribeDBInstancesInput{
|
|
DBInstanceIdentifier: aws.String(id),
|
|
})
|
|
require.NoError(t, err, "describe-db-instances")
|
|
require.Len(t, described.DBInstances, 1)
|
|
assert.Equal(t, map[string]string{"env": "e2e-updated"}, tagMap(described.DBInstances[0].TagList))
|
|
})
|
|
|
|
// The endpoint's name, its resolution inside the guest and the client
|
|
// connection itself belong to TestConnectivity: a lookup or a psql on the
|
|
// machine running the test proves nothing about the path a customer takes.
|
|
t.Run("PublishesAnEndpoint", func(t *testing.T) {
|
|
requireAvailable(t, instance)
|
|
host := aws.StringValue(instance.Endpoint.Address)
|
|
if f.BaseDomain == "" {
|
|
assert.NotNil(t, net.ParseIP(host),
|
|
"with no base domain the endpoint is the bare ENI address, got %s", host)
|
|
return
|
|
}
|
|
assert.Equal(t, fmt.Sprintf("%s.%s.%s.rds.%s", id, f.Account, f.Region, f.BaseDomain), host,
|
|
"the endpoint name is account-qualified so identifiers collide across tenants without colliding in DNS")
|
|
})
|
|
}
|
|
|
|
func tagMap(tags []*rds.Tag) map[string]string {
|
|
out := make(map[string]string, len(tags))
|
|
for _, tag := range tags {
|
|
out[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func requireAvailable(t *testing.T, instance *rds.DBInstance) {
|
|
t.Helper()
|
|
if instance == nil {
|
|
t.Skip("DB instance never reached available (BecomesAvailable failed)")
|
|
}
|
|
}
|