mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
40f8c4da94
- Rename 36 phaseN_X / phaseIAM_N funcs to runX. - Drop numeric suffix from 7 TestIAMN_X wrappers (TestIAM1_UserCRUD -> TestIAMUserCRUD). - Rewrite harness.Phase log strings "Phase N - X" -> "Single - X" to mirror multinode convention. - Add t.Parallel() to bucket #1: 10 read-only / own-everything top-level Tests (Environment, ClusterStatsCLI, Discovery, SerialConsoleAccess, KeyPairs, Image, TagManagement, AccountScoping, VPCSubnetE2E, RouteTableValidation). Sequential bucket retains source-order singleton lifecycle (InstanceLaunch through FinalClusterStats).
164 lines
5.9 KiB
Go
164 lines
5.9 KiB
Go
//go:build e2e
|
||
|
||
package single
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/aws/aws-sdk-go/aws"
|
||
"github.com/aws/aws-sdk-go/service/ec2"
|
||
"github.com/mulgadc/spinifex/tests/e2e/harness"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// runVolumeLifecycle exercises create → modify → attach → detach →
|
||
// delete on a fresh 10 GiB volume against the running Phase 5 instance.
|
||
// Maps to run-e2e.sh ~488–612.
|
||
func runVolumeLifecycle(t *testing.T, fix *Fixture) {
|
||
harness.Phase(t, "Single — Volume Lifecycle (Attach/Detach)")
|
||
|
||
az := needAZ(t, fix)
|
||
inst, _ := needInstance(t, fix)
|
||
instanceID := aws.StringValue(inst.InstanceId)
|
||
|
||
// This phase creates + deletes a standalone test volume; do NOT route
|
||
// through harness.EnsureVolume because the fixture's terminate-on-cleanup
|
||
// would later try to re-delete a volume this test has just deleted in-line.
|
||
harness.Step(t, "create-volume size=10 az=%s", az)
|
||
createOut, err := fix.AWS.EC2.CreateVolume(&ec2.CreateVolumeInput{
|
||
AvailabilityZone: aws.String(az),
|
||
Size: aws.Int64(10),
|
||
})
|
||
require.NoError(t, err, "create-volume")
|
||
volumeID := aws.StringValue(createOut.VolumeId)
|
||
require.NotEmpty(t, volumeID, "CreateVolume returned empty VolumeId")
|
||
harness.Detail(t, "volume", volumeID)
|
||
|
||
// Best-effort cleanup if a later assertion fails before the in-line delete.
|
||
t.Cleanup(func() {
|
||
_, _ = fix.AWS.EC2.DeleteVolume(&ec2.DeleteVolumeInput{VolumeId: aws.String(volumeID)})
|
||
})
|
||
|
||
harness.WaitForVolumeState(t, fix.AWS, volumeID, "available", harness.WithPoll(500*time.Millisecond))
|
||
|
||
const newSize int64 = 20
|
||
harness.Step(t, "modify-volume size=%d", newSize)
|
||
_, err = fix.AWS.EC2.ModifyVolume(&ec2.ModifyVolumeInput{
|
||
VolumeId: aws.String(volumeID),
|
||
Size: aws.Int64(newSize),
|
||
})
|
||
require.NoError(t, err, "modify-volume")
|
||
|
||
// Bash polls Volumes[0].Size directly (not Modifications[].State) — replicate.
|
||
// Resize is slower than state transitions; allow 5 minutes.
|
||
harness.EventuallyErr(t, func() error {
|
||
out, err := fix.AWS.EC2.DescribeVolumes(&ec2.DescribeVolumesInput{
|
||
VolumeIds: []*string{aws.String(volumeID)},
|
||
})
|
||
if err != nil {
|
||
return fmt.Errorf("describe-volumes: %w", err)
|
||
}
|
||
if len(out.Volumes) == 0 {
|
||
return fmt.Errorf("%s not found", volumeID)
|
||
}
|
||
if got := aws.Int64Value(out.Volumes[0].Size); got != newSize {
|
||
return fmt.Errorf("%s size=%d want=%d", volumeID, got, newSize)
|
||
}
|
||
return nil
|
||
}, 5*time.Minute, 5*time.Second)
|
||
harness.Detail(t, "resized_gib", newSize)
|
||
|
||
harness.Step(t, "attach-volume %s -> %s as /dev/sdf", volumeID, instanceID)
|
||
_, err = fix.AWS.EC2.AttachVolume(&ec2.AttachVolumeInput{
|
||
VolumeId: aws.String(volumeID),
|
||
InstanceId: aws.String(instanceID),
|
||
Device: aws.String("/dev/sdf"),
|
||
})
|
||
require.NoError(t, err, "attach-volume")
|
||
|
||
harness.WaitForVolumeState(t, fix.AWS, volumeID, ec2.VolumeStateInUse, harness.WithPoll(500*time.Millisecond))
|
||
|
||
// Once the volume is in-use, the attachment record should be populated.
|
||
descAttached, err := fix.AWS.EC2.DescribeVolumes(&ec2.DescribeVolumesInput{
|
||
VolumeIds: []*string{aws.String(volumeID)},
|
||
})
|
||
require.NoError(t, err, "describe-volumes (attached)")
|
||
require.NotEmpty(t, descAttached.Volumes[0].Attachments, "no Attachments after attach-volume")
|
||
att := descAttached.Volumes[0].Attachments[0]
|
||
assert.Equal(t, ec2.VolumeAttachmentStateAttached, aws.StringValue(att.State),
|
||
"attachment State should be %q", ec2.VolumeAttachmentStateAttached)
|
||
assert.Equal(t, instanceID, aws.StringValue(att.InstanceId), "Attachment.InstanceId mismatch")
|
||
|
||
// Bash omits --instance-id to exercise the gateway's resolution path.
|
||
harness.Step(t, "detach-volume %s", volumeID)
|
||
_, err = fix.AWS.EC2.DetachVolume(&ec2.DetachVolumeInput{
|
||
VolumeId: aws.String(volumeID),
|
||
})
|
||
require.NoError(t, err, "detach-volume")
|
||
|
||
harness.WaitForVolumeState(t, fix.AWS, volumeID, "available", harness.WithPoll(500*time.Millisecond))
|
||
|
||
harness.Step(t, "delete-volume %s", volumeID)
|
||
_, err = fix.AWS.EC2.DeleteVolume(&ec2.DeleteVolumeInput{
|
||
VolumeId: aws.String(volumeID),
|
||
})
|
||
require.NoError(t, err, "delete-volume")
|
||
|
||
// Bash treats describe-volume's first non-zero exit OR empty/None result
|
||
// as proof of deletion. We assert on InvalidVolume.NotFound specifically —
|
||
// any other error or a successful describe returning a non-deleted state
|
||
// surfaces the bug instead of being papered over.
|
||
harness.EventuallyErr(t, func() error {
|
||
out, err := fix.AWS.EC2.DescribeVolumes(&ec2.DescribeVolumesInput{
|
||
VolumeIds: []*string{aws.String(volumeID)},
|
||
})
|
||
if err != nil {
|
||
if harness.ErrorCodeIs(err, "InvalidVolume.NotFound") {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("describe-volumes: %w", err)
|
||
}
|
||
if len(out.Volumes) == 0 {
|
||
return nil
|
||
}
|
||
if state := aws.StringValue(out.Volumes[0].State); state == "deleted" {
|
||
return nil
|
||
} else {
|
||
return errors.New("volume still present: state=" + state)
|
||
}
|
||
}, 2*time.Minute, 2*time.Second)
|
||
}
|
||
|
||
// runVolumeStatus runs DescribeVolumeStatus against the root volume
|
||
// and asserts the response references it back. Maps to run-e2e.sh ~614–625.
|
||
func runVolumeStatus(t *testing.T, fix *Fixture) {
|
||
harness.Phase(t, "Single — DescribeVolumeStatus")
|
||
|
||
_, rootVolumeID := needInstance(t, fix)
|
||
|
||
out, err := fix.AWS.EC2.DescribeVolumeStatus(&ec2.DescribeVolumeStatusInput{
|
||
VolumeIds: []*string{aws.String(rootVolumeID)},
|
||
})
|
||
require.NoError(t, err, "describe-volume-status %s", rootVolumeID)
|
||
require.NotEmpty(t, out.VolumeStatuses, "no VolumeStatuses returned")
|
||
|
||
var matched bool
|
||
var status string
|
||
for _, vs := range out.VolumeStatuses {
|
||
if aws.StringValue(vs.VolumeId) == rootVolumeID {
|
||
matched = true
|
||
if vs.VolumeStatus != nil {
|
||
status = aws.StringValue(vs.VolumeStatus.Status)
|
||
}
|
||
break
|
||
}
|
||
}
|
||
assert.Truef(t, matched, "DescribeVolumeStatus did not return %s; got %d entries",
|
||
rootVolumeID, len(out.VolumeStatuses))
|
||
harness.Detail(t, "volume", rootVolumeID, "status", status)
|
||
}
|