mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-21 19:56:33 +00:00
48012a4058
- Rename .claude/rules/hive.md → spinifex.md - Update CLAUDE.md component description - Rename doc directories: hive-admin-cli → spinifex-admin-cli, etc. - Rename doc files: hive-cluster-stats, hive-code-consolidation, etc. - Bulk update all markdown files across docs/ tree - Update internal docs: CLUSTER-INSTALL, TESTING, DEVNOTES, BRANCHES
677 lines
28 KiB
Markdown
677 lines
28 KiB
Markdown
# E2E Test Coverage
|
|
|
|
## Single-Node (`run-e2e.sh`)
|
|
|
|
### Phase 1: Environment Setup
|
|
- KVM support check
|
|
- `spx admin init` (region/az/node config)
|
|
- CA certificate trust
|
|
- Start all services (`start-dev.sh`)
|
|
- Wait for AWS gateway on `localhost:9999`
|
|
|
|
### Phase 1b: Cluster Stats CLI
|
|
- `spx get nodes` — verify node shows Ready
|
|
- `spx top nodes` — verify CPU/MEM resource stats
|
|
- `spx get vms` — verify "No VMs found" before any launches
|
|
|
|
### Phase 2: Discovery & Metadata
|
|
- `describe-regions`
|
|
- `describe-availability-zones` (verify zone name and state)
|
|
- `describe-instance-types` (discover available types)
|
|
- Select nano instance type and detect architecture
|
|
|
|
### Phase 2b: Serial Console Access Settings
|
|
- `get-serial-console-access-status` (verify default disabled)
|
|
- `enable-serial-console-access` (verify returns true, get confirms true)
|
|
- `disable-serial-console-access` (verify returns false, get confirms false)
|
|
|
|
### Phase 3: SSH Key Management
|
|
- `create-key-pair` (test-key-1, verify private key material)
|
|
- `import-key-pair` (test-key-2, from local RSA key)
|
|
- `describe-key-pairs` (verify both keys exist)
|
|
- `delete-key-pair` (test-key-2, verify only test-key-1 remains)
|
|
|
|
### Phase 4: Image Management
|
|
- `spx admin images import` (file-based, architecture-aware)
|
|
- `describe-images` (verify AMI by ID)
|
|
|
|
### Phase 5: Instance Lifecycle
|
|
- `run-instances` (launch VM with key pair)
|
|
- `describe-instances` (poll pending -> running)
|
|
|
|
### Phase 5a-pre: Cluster Stats CLI (with running VM)
|
|
- `spx get vms` — verify running instance appears in output
|
|
|
|
### Phase 5a: Instance Metadata Validation
|
|
- `describe-instances` — verify InstanceType matches requested type
|
|
- Verify KeyName matches requested key
|
|
- Verify ImageId matches requested AMI
|
|
- Verify at least 1 BlockDeviceMapping present
|
|
|
|
### Phase 5a-ii: SSH Connectivity & Volume Verification
|
|
- SSH into instance via QEMU hostfwd port
|
|
- Verify SSH connectivity (`id` command returns ec2-user)
|
|
- Verify root volume size from inside VM matches API-reported size (`lsblk` vs `describe-volumes`)
|
|
- Verify VM hostname
|
|
|
|
### Phase 5a-iii: Console Output
|
|
- `get-console-output` succeeds (verify InstanceId in response)
|
|
|
|
### Phase 5 (cont): Root Volume
|
|
- `describe-volumes` (verify root volume attached)
|
|
|
|
### Phase 5b: Volume Lifecycle
|
|
- `create-volume` (10GB, ap-southeast-2a)
|
|
- `modify-volume` (resize to 20GB, poll to verify)
|
|
- `attach-volume` (to running instance, /dev/sdf)
|
|
- `describe-volumes` (verify in-use + attached state)
|
|
- `detach-volume` (verify available state)
|
|
- `delete-volume` (verify gone)
|
|
|
|
### Phase 5b-ii: DescribeVolumeStatus
|
|
- `describe-volume-status` (on root volume, verify VolumeId in response)
|
|
|
|
### Phase 5c: Snapshot Lifecycle
|
|
- Uses root volume already attached to running instance (snapshots require a mounted VB instance)
|
|
- `create-snapshot` (from attached root volume, with description)
|
|
- Verify create response fields (VolumeId, VolumeSize, State, Progress)
|
|
- `describe-snapshots` (by ID, verify VolumeId/Size/Description)
|
|
- `copy-snapshot` (with new description, verify distinct ID)
|
|
- `describe-snapshots` (verify both original + copy visible)
|
|
- `delete-snapshot` (original, verify gone while copy survives)
|
|
- `delete-snapshot` (copy, cleanup)
|
|
|
|
### Phase 5d: Verify Snapshot-Backed Instance Launch
|
|
- All `run-instances` calls use the snapshot path (`cloneAMIToVolume` -> `OpenFromSnapshot`), so the Phase 5 instance is already snapshot-backed
|
|
- Verify AMI snapshot exists in Predastore (`snap-{amiId}/config.json`)
|
|
- Read Phase 5 root volume's `config.json` from Predastore
|
|
- Verify `SnapshotID` and `SourceVolumeName` are set (proves zero-copy clone)
|
|
|
|
### Phase 5e: CreateImage Lifecycle
|
|
- `create-image` (from running instance, with name and description)
|
|
- Verify returned ImageId is non-empty
|
|
- `describe-images` (verify custom AMI name and state)
|
|
- Extract backing snapshot ID from Predastore config (for cleanup before termination)
|
|
|
|
### Phase 6: Tag Management
|
|
- `create-tags` (3 tags on instance)
|
|
- `describe-tags` (filter by resource-id)
|
|
- `create-tags` (2 tags on volume)
|
|
- `describe-tags` (filter by key)
|
|
- `describe-tags` (filter by resource-type)
|
|
- `create-tags` (overwrite existing tag value)
|
|
- `delete-tags` (unconditional by key)
|
|
- `delete-tags` (with wrong value — should be no-op)
|
|
- `delete-tags` (with correct value)
|
|
- Verify final tag count
|
|
|
|
### Phase 7: Instance State Transitions
|
|
- `stop-instances` (poll -> stopped)
|
|
|
|
### Phase 7a: Attach Volume to Stopped Instance (Error Path)
|
|
- `create-volume` (for attach test)
|
|
- `attach-volume` to stopped instance (expect `IncorrectInstanceState` error)
|
|
- `delete-volume` (cleanup)
|
|
|
|
### Phase 7b: ModifyInstanceAttribute
|
|
- `modify-instance-attribute` (change instance type from nano → xlarge while stopped)
|
|
- `describe-instances` (verify type updated in KV)
|
|
- `describe-instance-types` (get expected vCPU count and memory for new type)
|
|
- `start-instances` (poll -> running with new type)
|
|
- SSH: `nproc` — verify vCPU count matches xlarge (4 vCPUs)
|
|
- SSH: `/proc/meminfo` MemTotal — verify memory matches xlarge (within 85% of expected)
|
|
|
|
### Phase 7c: RunInstances with count > 1
|
|
- `run-instances --count 2` (launch 2 instances in a single call)
|
|
- Verify 2 instances returned in response
|
|
- Poll both to running state
|
|
- `terminate-instances` (both, poll -> terminated)
|
|
|
|
### Phase 8: Negative / Error Path Tests
|
|
- `run-instances` with malformed AMI ID (expect `InvalidAMIID.Malformed`)
|
|
- `run-instances` with invalid instance type (expect `InvalidInstanceType`)
|
|
- `attach-volume` on in-use volume (expect `VolumeInUse`)
|
|
- `detach-volume` on boot volume (expect `OperationNotPermitted`)
|
|
- `delete-snapshot` on non-existent snapshot (expect `InvalidSnapshot.NotFound`)
|
|
- Unsupported Action via raw HTTP (expect `InvalidAction` or error response)
|
|
- `run-instances` with non-existent AMI ID (expect `InvalidAMIID.NotFound`)
|
|
- `run-instances` with non-existent key pair (expect `InvalidKeyPair.NotFound`)
|
|
- `delete-volume` on non-existent volume (expect `InvalidVolume.NotFound`)
|
|
- `create-key-pair` with duplicate name (expect `InvalidKeyPair.Duplicate`)
|
|
- `import-key-pair` with duplicate name (expect `InvalidKeyPair.Duplicate`)
|
|
- `import-key-pair` with invalid key format (expect `InvalidKey.Format`)
|
|
- `describe-volumes` with non-existent volume ID (expect `InvalidVolume.NotFound`)
|
|
- `describe-images` with non-existent AMI ID (expect `InvalidAMIID.NotFound`)
|
|
- `create-image` with duplicate name (expect `InvalidAMIName.Duplicate`)
|
|
- `delete-key-pair` on non-existent key (expect success — idempotent, matches AWS)
|
|
- `modify-instance-attribute` on running instance (expect `InvalidInstanceID.NotFound` — running instances not in stopped KV)
|
|
|
|
### IAM Phase 1: User CRUD
|
|
- Root auth via `iam list-users` (root user exists)
|
|
- `create-user` (alice, bob with path)
|
|
- Duplicate user (expect `EntityAlreadyExists`)
|
|
- `get-user` (alice)
|
|
- `get-user` non-existent (expect `NoSuchEntity`)
|
|
- `list-users` (verify count)
|
|
- `list-users` with `--path-prefix` filter
|
|
|
|
### IAM Phase 2: Access Key Lifecycle
|
|
- `create-access-key` (alice key 1, key 2)
|
|
- Third key exceeds limit (expect `LimitExceeded`)
|
|
- `create-access-key` for non-existent user (expect `NoSuchEntity`)
|
|
- `list-access-keys` (alice: 2 keys, bob: 0 keys)
|
|
- `update-access-key` (Inactive, verify status, reactivate)
|
|
- `delete-access-key` (verify count decremented)
|
|
|
|
### IAM Phase 3: User Authentication
|
|
- Configure IAM user profile with access key
|
|
- Deactivate key → auth rejected (`InvalidClientTokenId`)
|
|
- Reactivate key
|
|
- Wrong secret → `SignatureDoesNotMatch`
|
|
- Non-existent key ID → `InvalidClientTokenId`
|
|
- Multi-user simultaneous auth (root + bob)
|
|
|
|
### IAM Phase 4: Policy CRUD
|
|
- `create-policy` (EC2ReadOnly, FullAdmin, DenyTerminate, IAMReadOnly, EC2DescribeAll)
|
|
- Duplicate policy (expect `EntityAlreadyExists`)
|
|
- Malformed JSON (expect `MalformedPolicyDocument`)
|
|
- `get-policy` (by ARN)
|
|
- `get-policy` non-existent (expect `NoSuchEntity`)
|
|
- `get-policy-version` (v1)
|
|
- `list-policies` (verify count)
|
|
|
|
### IAM Phase 5: Policy Attachment & Enforcement
|
|
- Create charlie with access key
|
|
- `attach-user-policy` (alice: EC2ReadOnly + IAMReadOnly, bob: DenyTerminate)
|
|
- `list-attached-user-policies` (verify count)
|
|
- Idempotent attach (no duplicate)
|
|
- Attach non-existent policy (expect `NoSuchEntity`)
|
|
- Attach to non-existent user (expect `NoSuchEntity`)
|
|
- **Default Deny**: charlie (no policies) → `AccessDenied` on ec2 + iam
|
|
- **Explicit Allow**: alice → ec2:Describe{Instances,Vpcs} allowed, ec2:DescribeKeyPairs denied, iam:ListUsers allowed, iam:CreateUser denied
|
|
- **Deny Override**: bob → ec2:Describe allowed, ec2:TerminateInstances denied (explicit Deny), iam denied
|
|
- **Root Bypass**: root user → all actions succeed
|
|
- **Prefix Wildcard**: swap alice to EC2DescribeAll (ec2:Describe*) → Describe* allowed, non-Describe denied
|
|
- **FullAdmin**: charlie with FullAdmin → all actions allowed
|
|
|
|
### IAM Phase 6: Policy Lifecycle — Detach & Delete
|
|
- `detach-user-policy` → user loses access
|
|
- `delete-policy` while attached (expect `DeleteConflict`)
|
|
- Detach then delete → `get-policy` returns `NoSuchEntity`
|
|
|
|
### IAM Phase 7: IAM Cleanup
|
|
- Delete all test users (remove keys + policies first)
|
|
- Verify root-only remains (`list-users` count = 1)
|
|
- Delete all test policies (`list-policies` count = 0)
|
|
- Clean up AWS CLI profiles
|
|
|
|
### Phase 8: EC2 Account Scoping
|
|
|
|
Tests that EC2 resources are properly isolated between tenant accounts (Alpha, Beta). Based on `docs/development/feature/iam-phase4-e2e-test-guide.md`. Skips CreateImage (mulga-612) and instance tags (mulga-613).
|
|
|
|
#### Step 1: Account Setup
|
|
- `spx admin account create` (Team Alpha, Team Beta)
|
|
- Configure AWS CLI profiles
|
|
- Verify auth for both accounts
|
|
|
|
#### Step 2: Instance Scoping
|
|
- Alpha + Beta each launch 1 instance
|
|
- `describe-instances` isolation (each sees only own)
|
|
- OwnerId field matches account ID
|
|
- Cross-account `stop-instances` → `InvalidInstanceID.NotFound`
|
|
- Cross-account `terminate-instances` → `InvalidInstanceID.NotFound`
|
|
- Cross-account `start-instances` → `InvalidInstanceID.NotFound`
|
|
- Cross-account `modify-instance-attribute` → `InvalidInstanceID.NotFound`
|
|
- Cross-account `get-console-output` → `InvalidInstanceID.NotFound`
|
|
|
|
#### Step 3: Volume Scoping
|
|
- Alpha + Beta each create 1 volume
|
|
- `describe-volumes` isolation
|
|
- Cross-account `describe-volumes` by ID → `InvalidVolume.NotFound`
|
|
- Cross-account `delete-volume` → `InvalidVolume.NotFound`
|
|
- Cross-account `attach-volume` → `InvalidVolume.NotFound`
|
|
- Cross-account `detach-volume` → `InvalidVolume.NotFound`
|
|
- Cross-account `modify-volume` → `InvalidVolume.NotFound`
|
|
|
|
#### Step 4: Key Pair Scoping
|
|
- Alpha + Beta each create key pair
|
|
- `describe-key-pairs` isolation
|
|
- Same key name in both accounts (namespace isolation — different KeyPairIds)
|
|
- Cross-account `delete-key-pair` → no effect on other account's key
|
|
- `import-key-pair` → invisible to other account
|
|
|
|
#### Step 5: Snapshot Scoping
|
|
- Alpha + Beta each create snapshot from own volume
|
|
- `describe-snapshots` isolation + OwnerId verification
|
|
- Cross-account `delete-snapshot` → `UnauthorizedOperation`
|
|
- Cross-account `create-snapshot` from other's volume → `InvalidVolume.NotFound`
|
|
|
|
#### Step 6: VPC/Subnet Scoping
|
|
- Alpha + Beta each create VPC (same CIDR — no conflict)
|
|
- `describe-vpcs` isolation
|
|
- Cross-account `describe-vpcs` by ID → `InvalidVpcID.NotFound`
|
|
- Cross-account `delete-vpc` → `InvalidVpcID.NotFound`
|
|
- Alpha + Beta each create subnet
|
|
- `describe-subnets` isolation
|
|
- Cross-account `create-subnet` in other's VPC → `InvalidVpcID.NotFound`
|
|
- Cross-account `delete-subnet` → `InvalidSubnetID.NotFound`
|
|
|
|
#### Step 7: IGW + EIGW Scoping
|
|
- Alpha + Beta each create IGW
|
|
- `describe-internet-gateways` isolation
|
|
- Cross-account `describe-internet-gateways` by ID → `InvalidInternetGatewayID.NotFound`
|
|
- Cross-account `delete-internet-gateway` → `InvalidInternetGatewayID.NotFound`
|
|
- Cross-account `attach-internet-gateway` → `InvalidInternetGatewayID.NotFound`
|
|
- Cross-account `detach-internet-gateway` → `InvalidInternetGatewayID.NotFound`
|
|
- Alpha + Beta each create EIGW
|
|
- `describe-egress-only-internet-gateways` isolation
|
|
- Cross-account EIGW delete → no effect
|
|
|
|
#### Step 8: Account Settings
|
|
- Alpha `enable-ebs-encryption-by-default` → Beta unaffected
|
|
- Independent toggle verification
|
|
|
|
#### Step 9: Global Resources
|
|
- `describe-regions` identical for both accounts
|
|
- `describe-availability-zones` identical
|
|
- `describe-instance-types` identical
|
|
|
|
#### Step 10: Edge Cases
|
|
- Empty account (Gamma) — no resources visible
|
|
- Root isolation from tenants (root cannot see tenant instances)
|
|
- Non-existent resource IDs → same error as cross-account (no info leakage)
|
|
|
|
#### Step 11: Cleanup
|
|
- Terminate instances, delete volumes/snapshots/keys/VPCs/IGWs/EIGWs/subnets
|
|
- Clean up AWS CLI profiles
|
|
|
|
### Phase 9: Terminate and Verify Cleanup
|
|
- `delete-snapshot` (CreateImage backing snapshot, so DeleteOnTermination is not blocked)
|
|
- `terminate-instances` (poll -> terminated)
|
|
|
|
### Phase 9a: SSH Unreachable Verification
|
|
- Verify SSH connection is refused/unreachable after termination
|
|
|
|
### Phase 9b: Volume Cleanup Verification
|
|
- `describe-volumes` on root volume after termination
|
|
- Verify root volume is deleted (DeleteOnTermination)
|
|
|
|
---
|
|
|
|
## Pseudo Multi-Node (`run-pseudo-multinode-e2e.sh`)
|
|
|
|
### Phase 1: Environment Setup
|
|
- KVM support check
|
|
- Simulated network IPs (10.11.12.{1,2,3} on loopback)
|
|
- Simulated network IPs (no ramdisk — start-dev.sh uses disk-backed WAL/VB in CI)
|
|
|
|
### Phase 2: Cluster Initialization
|
|
- `spx admin init` (leader node1)
|
|
- CA certificate trust
|
|
- Start node1 services
|
|
- `spx admin join` (node2, node3)
|
|
- Start node2 + node3 services
|
|
|
|
### Phase 3: Cluster Health Verification
|
|
- Verify NATS cluster (3 nodes)
|
|
- Verify Predastore cluster (3 nodes)
|
|
- Wait for gateway on node1
|
|
- Wait for daemon NATS readiness
|
|
- `describe-regions` (gateway connectivity check)
|
|
|
|
### Phase 3b: Cluster Stats CLI
|
|
- `spx get nodes` — verify all 3 nodes show Ready
|
|
- `spx top nodes` — verify instance type capacity table
|
|
- `spx get vms` — verify empty (no instances yet)
|
|
|
|
### Phase 4: Image and Key Setup
|
|
- `describe-instance-types` (discover + select nano)
|
|
- `create-key-pair`
|
|
- `spx admin images import` (with node1 config paths)
|
|
- `describe-images` (verify AMI)
|
|
|
|
### Phase 4b: Multi-Node Key Pair Operations
|
|
- `import-key-pair` (multinode-test-key-2, from local RSA key)
|
|
- `describe-key-pairs` (verify both keys visible across cluster)
|
|
- `delete-key-pair` (multinode-test-key-2, verify deletion)
|
|
|
|
### Phase 5: Multi-Node Instance Tests
|
|
|
|
#### Test 1: Instance Distribution
|
|
- `run-instances` x3 (distribute across nodes)
|
|
- Poll all instances to running state
|
|
- Check instance distribution across nodes
|
|
- `spx get vms` — verify all instances visible
|
|
|
|
#### Test 1a-ii: SSH Connectivity & Volume Verification
|
|
- SSH into all 3 instances via QEMU hostfwd port
|
|
- Verify SSH connectivity (`id` command returns ec2-user)
|
|
- Verify root volume size from inside VM matches API-reported size (`lsblk` vs `describe-volumes`)
|
|
- Verify VM hostname
|
|
|
|
#### Test 1b: Volume Lifecycle
|
|
- `create-volume` (10GB)
|
|
- `modify-volume` (resize to 20GB)
|
|
- `attach-volume` (to first instance)
|
|
- `detach-volume`
|
|
- `delete-volume`
|
|
|
|
#### Test 1c: Snapshot Lifecycle
|
|
- Uses root volume of first instance (snapshots require a mounted VB instance)
|
|
- `create-snapshot` (from attached root volume, with description)
|
|
- Verify create response fields (VolumeId, VolumeSize, State)
|
|
- `describe-snapshots` (by ID, verify fields)
|
|
- `copy-snapshot` (with new description)
|
|
- `describe-snapshots` (verify both exist)
|
|
- `delete-snapshot` (original, verify copy survives)
|
|
- `delete-snapshot` (copy, cleanup)
|
|
|
|
#### Test 1c-ii: Verify Snapshot-Backed Instance Launch
|
|
- All `run-instances` calls use the snapshot path (`cloneAMIToVolume` -> `OpenFromSnapshot`), so the Test 1 instances are already snapshot-backed
|
|
- Verify AMI snapshot exists in Predastore (`snap-{amiId}/config.json`)
|
|
- Read first instance's root volume `config.json` from Predastore
|
|
- Verify `SnapshotID` and `SourceVolumeName` are set (proves zero-copy clone)
|
|
|
|
#### Test 1d: Tag Management (Instances)
|
|
- `create-tags` (3 tags on instance)
|
|
- `describe-tags` (filter by resource-id)
|
|
- `describe-tags` (filter by key)
|
|
- `describe-tags` (filter by resource-type)
|
|
- `create-tags` (overwrite tag value)
|
|
- `delete-tags` (unconditional by key)
|
|
- `delete-tags` (wrong value — no-op)
|
|
- `delete-tags` (correct value)
|
|
- Verify final tag count
|
|
|
|
#### Test 1d-ii: Tag Management (Volumes)
|
|
- `create-tags` (2 tags on root volume)
|
|
- `describe-tags` (filter by resource-id, verify count)
|
|
- `describe-tags` (filter by resource-type=volume)
|
|
- `delete-tags` (both tags, verify cleanup)
|
|
|
|
#### Test 2: DescribeInstances Aggregation
|
|
- `describe-instances` (fan-out across all nodes, verify count)
|
|
|
|
#### Test 3: Cross-Node Operations
|
|
- `stop-instances` (poll -> stopped)
|
|
- `start-instances` (poll -> running)
|
|
|
|
#### Test 4: NATS Cluster Health (Post-Operations)
|
|
- Verify NATS cluster still healthy after all operations
|
|
|
|
#### Test 5: VM Crash Recovery
|
|
- Kill QEMU process with SIGKILL (simulate OOM kill)
|
|
- Verify daemon detects crash (instance transitions to error/pending)
|
|
- Wait for auto-restart (backoff starts at 5s)
|
|
- Verify new QEMU PID differs from original
|
|
- Verify instance reaches running state
|
|
- Verify SSH connectivity after recovery
|
|
|
|
#### Test 5b: Crash Loop Prevention
|
|
- Kill QEMU 4 times rapidly on a third instance
|
|
- Verify crash loop is detected and restarts stop after max attempts (3 in 10-min window)
|
|
- Verify instance reaches error state (won't restart further)
|
|
|
|
### Phase 5c: IAM Accounts & Cross-Account Isolation
|
|
|
|
#### Step 1: Create Accounts
|
|
- `spx admin account create` (Team Alpha, Team Beta)
|
|
- Verify sequential 12-digit account IDs
|
|
- `spx admin account list` (verify both accounts)
|
|
|
|
#### Step 2: Account Admin Auth
|
|
- Alpha admin authenticates via ec2 + iam
|
|
- Beta admin authenticates via ec2 + iam
|
|
|
|
#### Step 3: Account-Scoped Users
|
|
- `create-user` alice in Alpha + Beta (same name, different accounts)
|
|
- `create-user` team-member (Alpha), dev-user (Beta)
|
|
- `list-users` scoped per account (verify different user lists)
|
|
- Cross-account isolation: Alpha cannot see Beta's users and vice versa (`NoSuchEntity`)
|
|
|
|
#### Step 4: Account-Scoped Access Keys
|
|
- `create-access-key` for alice in Alpha + Beta
|
|
- Configure separate AWS CLI profiles per account user
|
|
|
|
#### Step 5: Account-Scoped Policies & Enforcement
|
|
- `create-policy` EC2ReadOnly in Alpha (narrow: DescribeInstances + DescribeVpcs)
|
|
- `create-policy` EC2ReadOnly in Beta (broad: ec2:Describe*)
|
|
- `attach-user-policy` to alice in both accounts
|
|
- Alpha alice: DescribeInstances allowed, DescribeKeyPairs denied (narrow)
|
|
- Beta alice: DescribeInstances allowed, DescribeKeyPairs allowed (broad Describe*)
|
|
- Both denied: CreateKeyPair
|
|
|
|
#### Step 6: Cross-Account Delete Isolation
|
|
- Delete Alpha's alice (detach policy, delete key, delete user)
|
|
- Verify Alpha alice gone (`NoSuchEntity`)
|
|
- Verify Beta alice unaffected (still exists, auth still works)
|
|
|
|
#### Step 7: EC2 Resource Scoping
|
|
|
|
Tests that EC2 resources are properly isolated between the Alpha/Beta accounts. Skips CreateImage (mulga-612) and instance tags (mulga-613).
|
|
|
|
**7a: Instance Scoping**
|
|
- Alpha + Beta each launch 1 instance
|
|
- `describe-instances` isolation (each sees only own)
|
|
- OwnerId field matches account ID
|
|
- Cross-account `stop-instances` → `InvalidInstanceID.NotFound`
|
|
- Cross-account `terminate-instances` → `InvalidInstanceID.NotFound`
|
|
- Cross-account `get-console-output` → `InvalidInstanceID.NotFound`
|
|
|
|
**7b: Volume Scoping**
|
|
- Alpha + Beta each create 1 volume
|
|
- `describe-volumes` isolation
|
|
- Cross-account `describe-volumes` by ID → `InvalidVolume.NotFound`
|
|
- Cross-account `delete-volume` → `InvalidVolume.NotFound`
|
|
- Cross-account `attach-volume` → `InvalidVolume.NotFound`
|
|
- Cross-account `detach-volume` → `InvalidVolume.NotFound`
|
|
- Cross-account `modify-volume` → `InvalidVolume.NotFound`
|
|
|
|
**7c: Key Pair Scoping**
|
|
- Alpha + Beta each create key pair
|
|
- `describe-key-pairs` isolation
|
|
- Same key name in both accounts (namespace isolation — different KeyPairIds)
|
|
- Cross-account `delete-key-pair` → no effect on other account's key
|
|
- `import-key-pair` → invisible to other account
|
|
|
|
**7d: Snapshot Scoping**
|
|
- Alpha + Beta each create snapshot from own volume
|
|
- `describe-snapshots` isolation + OwnerId verification
|
|
- Cross-account `delete-snapshot` → `UnauthorizedOperation`
|
|
- Cross-account `create-snapshot` from other's volume → `InvalidVolume.NotFound`
|
|
|
|
**7e: VPC/Subnet Scoping**
|
|
- Alpha + Beta each create VPC (same CIDR — no conflict)
|
|
- `describe-vpcs` / `describe-subnets` isolation
|
|
- Cross-account VPC/subnet operations → `NotFound`
|
|
|
|
**7f: IGW + EIGW Scoping**
|
|
- Alpha + Beta each create IGW + EIGW
|
|
- Describe isolation for both
|
|
- Cross-account attach/detach/delete → `NotFound`
|
|
|
|
**7g: Account Settings**
|
|
- `enable-ebs-encryption-by-default` per account — independent toggle
|
|
|
|
**7h: Global Resources**
|
|
- `describe-regions`, `describe-availability-zones`, `describe-instance-types` identical for both accounts
|
|
|
|
#### Step 8: Edge Cases
|
|
- Empty account (Gamma) — no resources visible
|
|
- Root isolation from tenants
|
|
- Non-existent resource IDs → same error as cross-account
|
|
- Parallel key creation (race condition) — no cross-contamination
|
|
|
|
#### Step 9: EC2 + IAM Cleanup
|
|
- Terminate instances, delete volumes/snapshots/keys/VPCs/IGWs/EIGWs/subnets
|
|
- Delete all IAM users, policies, access keys
|
|
- Clean up AWS CLI profiles
|
|
|
|
### Phase 5d: VPC Networking
|
|
- Step 1: `create-vpc` (10.100.0.0/16) + `create-subnet` (10.100.1.0/24)
|
|
- Step 2: `run-instances` x3 with `--subnet-id` (launch into VPC subnet)
|
|
- Poll all VPC instances to running state
|
|
- Step 3: Verify `PrivateIpAddress` in `describe-instances` for each instance
|
|
- Verify `SubnetId` and `VpcId` match requested values
|
|
- Verify at least 1 `NetworkInterface` per instance
|
|
- Verify all IPs are unique and in subnet range (10.100.1.x)
|
|
- Step 4: SSH + ping connectivity (skipped in CI — OVN DHCP wait too slow)
|
|
- Step 5: Stop/start IP persistence
|
|
- `stop-instances` (all VPC instances, poll -> stopped)
|
|
- Verify `PrivateIpAddress` persists in stopped state
|
|
- `start-instances` (all VPC instances, poll -> running)
|
|
- Verify `PrivateIpAddress` identical after restart
|
|
- Step 6: Cleanup — terminate VPC instances, delete subnet, delete VPC
|
|
|
|
### Phase 6: Cluster Shutdown + Restart
|
|
|
|
#### Test 6a: Dry-Run Shutdown
|
|
- `spx admin cluster shutdown --dry-run`
|
|
- Validate output contains all 5 phases (GATE, DRAIN, STORAGE, PERSIST, INFRA)
|
|
|
|
#### Test 6b: Coordinated Cluster Shutdown
|
|
- `spx admin cluster shutdown --force --timeout 60s`
|
|
- Verify all services down on all nodes (gateway, NATS, QEMU)
|
|
|
|
#### Test 6c: Cluster Restart + Recovery
|
|
- Restart all 3 node services
|
|
- Verify NATS cluster reforms (3 members)
|
|
- Wait for gateway and daemon readiness
|
|
- Smoke test: `describe-instance-types` returns valid results
|
|
|
|
#### Test 6d: Instance Relaunch + Terminate
|
|
- Wait for instances to finish relaunching after restart (pending → running/error)
|
|
- `terminate-instances` (all 3 instances)
|
|
- Poll all to terminated state
|
|
|
|
### Cleanup
|
|
- Coordinated cluster shutdown (with fallback to per-node PID stops)
|
|
- Remove simulated IPs
|
|
|
|
---
|
|
|
|
## Real Multi-Node (`run-multinode-e2e.sh`)
|
|
|
|
Runs on a real 3-node libvirt cluster provisioned by OpenTofu (`scripts/tofu-cluster/`). Each node is a separate VM with its own OVN, NATS, Predastore, and Spinifex daemon. Bootstrap (`bootstrap.sh`) handles all provisioning before the test script runs.
|
|
|
|
### Bootstrap (pre-test)
|
|
|
|
1. OpenTofu provisions 3 libvirt VMs (bottlebrush, ironbark, casuarina) with cloud-init
|
|
2. Wait for all nodes to be SSH-reachable
|
|
3. Copy SSH key to node1 for peer access
|
|
4. `git fetch` + checkout test branch on all nodes (all 3 repos)
|
|
5. `make build` on all nodes (parallel)
|
|
6. `setup-ovn.sh` — primary gets `--management`, secondaries connect to primary's OVN central
|
|
7. `spx admin init` on primary, `spx admin join` on secondaries (formation server handshake)
|
|
8. `start-dev.sh` on all nodes
|
|
9. Wait for `/health` (awsgw=ok) on all nodes
|
|
10. Install Spinifex CA certificate on all nodes
|
|
11. `import-key-pair` (spinifex-key) + `spx admin images import` (Ubuntu 24.04)
|
|
12. `create-vpc` (10.200.0.0/16) + `create-subnet` (10.200.1.0/24)
|
|
|
|
### Phase 1: Pre-flight Validation
|
|
- KVM support check (`/dev/kvm` writable)
|
|
- SSH connectivity to all peer nodes
|
|
|
|
### Phase 2: Cluster Health
|
|
- NATS cluster: verify 2 unique route peers (3-node quorum)
|
|
- Predastore reachable on all 3 nodes
|
|
- AWS gateway reachable on all 3 nodes
|
|
- Daemon readiness (`describe-instance-types` returns results)
|
|
- `spx get nodes` — verify all 3 nodes show Ready
|
|
- `spx get vms` — verify empty (no instances yet)
|
|
|
|
### Phase 3: Instance Lifecycle + Distribution
|
|
- Discover nano instance type and AMI (from bootstrap import)
|
|
- `run-instances` x3 with staggered launches
|
|
- Poll all instances to running state
|
|
- Check instance distribution across physical nodes (QEMU process check)
|
|
- Verify at least 2 different hosting nodes (non-deterministic, non-fatal if all on 1)
|
|
- `spx get vms` — verify all 3 instances visible
|
|
|
|
### Phase 4: SSH into Guest VMs
|
|
- For each instance across all nodes:
|
|
- Find hosting node via QEMU process scan
|
|
- Extract SSH hostfwd port from QEMU command line on remote node
|
|
- Wait for SSH readiness (up to 60s)
|
|
- Verify SSH connectivity (`id` returns ec2-user)
|
|
- Verify block device (`lsblk`)
|
|
|
|
### Phase 5: Volume Lifecycle
|
|
- `create-volume` (10GB)
|
|
- `attach-volume` (to first instance, /dev/sdf)
|
|
- Poll for in-use + attached state
|
|
- `detach-volume` (poll for available)
|
|
- `delete-volume` (verify gone)
|
|
|
|
### Phase 6: Cross-Node Gateway Access
|
|
- `describe-instances` via node1 gateway (baseline count)
|
|
- `describe-instances` via node2 and node3 gateways
|
|
- Verify all gateways return the same instance count (fan-out aggregation)
|
|
|
|
### Phase 7: Cross-Node Operations
|
|
- Find which node hosts instance 0
|
|
- Pick a different node's gateway for stop
|
|
- `stop-instances` via remote gateway (poll -> stopped)
|
|
- Pick a third node's gateway for start
|
|
- `start-instances` via another remote gateway (poll -> running)
|
|
|
|
### Phase 8: Node Failure
|
|
- `stop-dev.sh` on node2 (simulate node failure)
|
|
- Verify node1 still serves requests (`describe-instance-types`)
|
|
- Verify node3 still serves requests
|
|
- Check NATS degraded state (1 peer instead of 2)
|
|
- `describe-instances` still works from surviving nodes
|
|
|
|
### Phase 9: Node Recovery
|
|
- `start-dev.sh` on node2 (restart failed node)
|
|
- Wait for NATS cluster to reform (2 peers again)
|
|
- Verify node2 gateway is back
|
|
- `spx get nodes` — verify all 3 nodes Ready again
|
|
- Verify node2 serves requests after recovery
|
|
|
|
### Phase 10: Cleanup
|
|
- `terminate-instances` (all 3 instances, poll -> terminated)
|
|
|
|
---
|
|
|
|
## VPC (`run-vpc-e2e.sh`)
|
|
|
|
Standalone VPC networking test suite. Runs against a running Spinifex endpoint (configurable via `ENDPOINT` env var). OVN topology tests are skipped when OVN is unavailable.
|
|
|
|
### Phase 1: VPC CRUD
|
|
- `create-vpc` (10.99.0.0/16, verify VpcId)
|
|
- `describe-vpcs` (by ID, verify exactly 1 returned)
|
|
|
|
### Phase 2: Subnet CRUD
|
|
- `create-subnet` (10.99.1.0/24 in VPC, verify SubnetId)
|
|
- `describe-subnets` (by ID, verify exactly 1 returned)
|
|
|
|
### Phase 3: Internet Gateway CRUD
|
|
- `create-internet-gateway` (verify InternetGatewayId)
|
|
- `describe-internet-gateways` (by ID, verify exactly 1 returned)
|
|
|
|
### Phase 4: Internet Gateway Attach / Detach
|
|
- `attach-internet-gateway` (IGW to VPC)
|
|
- `describe-internet-gateways` (verify attachment VpcId)
|
|
- `delete-internet-gateway` on attached IGW (expect `DependencyViolation` or rejection)
|
|
- `detach-internet-gateway` (IGW from VPC)
|
|
- `describe-internet-gateways` (verify no attachments)
|
|
|
|
### Phase 5: OVN Topology Verification (requires OVN)
|
|
- Re-attach IGW for topology inspection
|
|
- Verify OVN logical switch exists for subnet
|
|
- Verify OVN logical router exists for VPC
|
|
- Verify OVN external switch exists (IGW attached)
|
|
- Verify SNAT rule on VPC router
|
|
- Verify default route (0.0.0.0/0) on VPC router
|
|
- Dump full OVN NB DB topology for debugging
|
|
- Detach IGW
|
|
- Skipped when `ovn-nbctl` is not available
|
|
|
|
### Phase 6: Cleanup
|
|
- `delete-internet-gateway`
|
|
- `delete-subnet`
|
|
- `delete-vpc`
|
|
- Verify OVN cleanup (no VPC routers remaining, when OVN available)
|