mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 10:52:04 +00:00
3f91a3dbcd
# Conflicts: # spinifex/daemon/daemon.go # spinifex/foundation/aws/arn/arn.go # spinifex/foundation/aws/arn/arn_test.go # spinifex/foundation/state/kvlease/bucket.go # spinifex/foundation/state/kvlease/bucket_test.go # spinifex/foundation/state/kvstore/wait.go # spinifex/foundation/state/kvstore/wait_test.go # spinifex/foundation/state/kvutil/bucket_creation_invariants_test.go # spinifex/foundation/state/kvutil/export_test.go # spinifex/foundation/state/kvutil/kvutil.go # spinifex/foundation/state/kvutil/kvutil_test.go # spinifex/foundation/state/kvutil/replicas.go # spinifex/foundation/state/kvutil/replicas_health_test.go # spinifex/foundation/state/kvutil/replicas_test.go # spinifex/foundation/state/kvutil/state_test.go # spinifex/gateway/auth.go # spinifex/gateway/bedrock/logging.go # spinifex/gateway/ec2_authz_test.go # spinifex/gateway/ecrapi/authz.go # spinifex/gateway/eks.go # spinifex/gateway/gateway_test.go # spinifex/handlers/ec2/eigw/service_impl.go # spinifex/handlers/ec2/igw/service_impl.go # spinifex/handlers/ec2/instance/service_impl.go # spinifex/handlers/ec2/launchtemplate/service_impl_test.go # spinifex/handlers/ec2/placementgroup/service_impl.go # spinifex/handlers/ec2/placementgroup/service_impl_test.go # spinifex/handlers/ec2/routetable/service_impl.go # spinifex/handlers/ec2/spotinstance/service_impl.go # spinifex/handlers/ec2/volume/service_impl.go # spinifex/handlers/ec2/vpc/eni.go # spinifex/handlers/ec2/vpc/security_group.go # spinifex/handlers/ec2/vpc/service_impl.go # spinifex/handlers/ecs/tags.go # spinifex/handlers/eks/k3s_ha_control_plane.go # spinifex/handlers/iam/groups.go # spinifex/handlers/iam/retry.go # spinifex/handlers/rds/subnetgroup_test.go # spinifex/handlers/sts/assume_role.go # spinifex/ingress/aws/query/parser_fuzz_test.go # spinifex/runtime/compute/cache/cache_test.go # spinifex/runtime/roles/nats/servername.go # spinifex/services/awsgw/awsgw.go # spinifex/services/viperblockd/volume_dirty.go # spinifex/utils/account.go
179 lines
6.0 KiB
Go
179 lines
6.0 KiB
Go
package loadgen
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/aws/aws-sdk-go/service/sts"
|
|
)
|
|
|
|
// The operations a run may drive. All but one are reads, deliberately: a load
|
|
// generator that creates guests would compete with the harness for the very
|
|
// capacity the harness sized the run against. CreateTags is the exception
|
|
// because a control plane whose reads are fast and whose writes are not is a
|
|
// broken control plane, and tagging costs no capacity.
|
|
var registry = map[string]Op{
|
|
"DescribeInstances": {
|
|
Name: "DescribeInstances", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
_, err := t.Clients.EC2.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{})
|
|
return err
|
|
},
|
|
},
|
|
"DescribeVpcs": {
|
|
Name: "DescribeVpcs", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
_, err := t.Clients.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{})
|
|
return err
|
|
},
|
|
},
|
|
"DescribeSubnets": {
|
|
Name: "DescribeSubnets", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
_, err := t.Clients.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{})
|
|
return err
|
|
},
|
|
},
|
|
"DescribeVolumes": {
|
|
Name: "DescribeVolumes", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
_, err := t.Clients.EC2.DescribeVolumesWithContext(ctx, &ec2.DescribeVolumesInput{})
|
|
return err
|
|
},
|
|
},
|
|
// The same read as DescribeVolumes but for one known id, which takes the
|
|
// handler's fast path and skips the bucket listing entirely. Run the two
|
|
// together and the gap between them is what the listing costs.
|
|
"DescribeVolumesByID": {
|
|
Name: "DescribeVolumesByID", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
if t.VolumeID == "" {
|
|
return fmt.Errorf("DescribeVolumesByID: no volume resolved for %s", t.Account)
|
|
}
|
|
_, err := t.Clients.EC2.DescribeVolumesWithContext(ctx, &ec2.DescribeVolumesInput{
|
|
VolumeIds: []*string{aws.String(t.VolumeID)},
|
|
})
|
|
return err
|
|
},
|
|
},
|
|
"DescribeSecurityGroups": {
|
|
Name: "DescribeSecurityGroups", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
_, err := t.Clients.EC2.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{})
|
|
return err
|
|
},
|
|
},
|
|
// The console login path. A cluster whose EC2 API is healthy but whose STS
|
|
// is slow feels broken to every customer signing in, and nothing else in
|
|
// the run would notice.
|
|
"GetSessionToken": {
|
|
Name: "GetSessionToken", Kind: KindRead,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
_, err := t.Clients.STS.GetSessionTokenWithContext(ctx, &sts.GetSessionTokenInput{})
|
|
return err
|
|
},
|
|
},
|
|
"CreateTags": {
|
|
Name: "CreateTags", Kind: KindWrite,
|
|
Call: func(ctx context.Context, t *Target) error {
|
|
if t.VPCID == "" {
|
|
return fmt.Errorf("CreateTags: no VPC resolved for %s", t.Account)
|
|
}
|
|
_, err := t.Clients.EC2.CreateTagsWithContext(ctx, &ec2.CreateTagsInput{
|
|
Resources: []*string{aws.String(t.VPCID)},
|
|
Tags: []*ec2.Tag{{
|
|
Key: aws.String("spx:loadgen"),
|
|
Value: aws.String(t.Account),
|
|
}},
|
|
})
|
|
return err
|
|
},
|
|
},
|
|
}
|
|
|
|
// DefaultOps is what a run drives when none are named: the three reads every
|
|
// console page and every terraform plan makes, so the number means something
|
|
// outside this harness.
|
|
var DefaultOps = []string{"DescribeInstances", "DescribeVpcs", "DescribeVolumes"}
|
|
|
|
// OpNames lists every operation the registry knows, for the usage text.
|
|
func OpNames() []string {
|
|
return slices.Sorted(maps.Keys(registry))
|
|
}
|
|
|
|
// ResolveOps turns names into operations, rejecting an unknown one rather than
|
|
// silently running a shorter list than was asked for.
|
|
func ResolveOps(names []string) ([]Op, error) {
|
|
ops := make([]Op, 0, len(names))
|
|
for _, name := range names {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
op, ok := registry[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("loadgen: unknown operation %q (known: %s)",
|
|
name, strings.Join(OpNames(), ", "))
|
|
}
|
|
ops = append(ops, op)
|
|
}
|
|
if len(ops) == 0 {
|
|
return nil, fmt.Errorf("loadgen: no operations selected")
|
|
}
|
|
return ops, nil
|
|
}
|
|
|
|
// NeedsVPC reports whether any selected operation acts on a tenant resource,
|
|
// so the caller knows whether to pay for discovery before the run starts.
|
|
func NeedsVPC(ops []Op) bool {
|
|
return slices.ContainsFunc(ops, func(op Op) bool {
|
|
return op.Name == "CreateTags"
|
|
})
|
|
}
|
|
|
|
// NeedsVolume reports whether any selected operation needs a volume id.
|
|
func NeedsVolume(ops []Op) bool {
|
|
return slices.ContainsFunc(ops, func(op Op) bool {
|
|
return op.Name == "DescribeVolumesByID"
|
|
})
|
|
}
|
|
|
|
// ResolveVolume finds one volume for the target to ask about by id. A tenant
|
|
// with a running guest has a root volume; one with none cannot measure the
|
|
// fast path, and saying so is better than measuring an empty listing instead.
|
|
func ResolveVolume(ctx context.Context, target *Target) error {
|
|
out, err := target.Clients.EC2.DescribeVolumesWithContext(ctx, &ec2.DescribeVolumesInput{})
|
|
if err != nil {
|
|
return fmt.Errorf("loadgen: resolve volume for %s: %w", target.Account, err)
|
|
}
|
|
for _, volume := range out.Volumes {
|
|
if volume.VolumeId != nil && *volume.VolumeId != "" {
|
|
target.VolumeID = *volume.VolumeId
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("loadgen: account %s has no volume to ask about", target.Account)
|
|
}
|
|
|
|
// ResolveVPC finds a VPC for the target to act on. Every account has a default
|
|
// VPC created with it, so an empty answer means something is wrong with the
|
|
// tenant rather than that it has nothing yet.
|
|
func ResolveVPC(ctx context.Context, target *Target) error {
|
|
out, err := target.Clients.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{})
|
|
if err != nil {
|
|
return fmt.Errorf("loadgen: resolve VPC for %s: %w", target.Account, err)
|
|
}
|
|
for _, vpc := range out.Vpcs {
|
|
if vpc.VpcId != nil && *vpc.VpcId != "" {
|
|
target.VPCID = *vpc.VpcId
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("loadgen: account %s has no VPC to act on", target.Account)
|
|
}
|