squall

In-memory stand-ins for cloud APIs that fail on cue, for testing code that must
survive partial failure.
Code that creates cloud resources has to cope with calls that fail after they
took effect: an insert that succeeded but whose response was lost, an operation
that ended in an error, a retry that finds the resource already there. Those
paths are where leaked resources and stuck records come from, and they are
hard to reach against a real cloud. A squall server answers the same API
calls the official client libraries make, keeps the resources in memory, and
fails exactly the calls you choose, or a seeded random share of them, so a
failure replays from its seed.
Packages
| Package |
What it does |
fault |
Decides which calls fail and how: rejected, applied with the response lost, applied with an error, an operation that ends in an error, or slow. Scripted rules and a seeded random rate. |
aws/ec2 |
Amazon EC2: Elastic IP addresses (allocate, describe with filters, release, associate, disassociate) and network interfaces (create with client tokens, describe with filters and pages, delete). |
gcp/compute |
Compute Engine: regional addresses (get, list with filters, insert, delete, setLabels) and region operations. |
Coverage grows with use: each stand-in implements the methods someone needed,
answers anything else with 404, and documents how faithful it is. Contributions
of more methods and services are welcome.
Example
faults := fault.New().Add(fault.Rule{
Method: "compute.addresses.insert",
Nth: 1,
Fault: fault.LoseResponse(), // takes effect, but the caller never hears back
})
s := compute.New(compute.WithFaults(faults))
srv := httptest.NewServer(s)
defer srv.Close()
client, _ := gcompute.NewAddressesRESTClient(ctx,
option.WithEndpoint(srv.URL), option.WithoutAuthentication())
// Run the code under test with client, then check what it left behind:
for _, a := range s.Addresses() {
fmt.Println(a.Name, a.Address, a.Labels)
}
For a property test, inject random faults and check that every seed converges:
for seed := range uint64(100) {
faults := fault.New(fault.Seed(seed)).Random(0.3,
fault.Unavailable(), fault.LoseResponse(), fault.FailAfter(500))
// ... run the code under test against compute.New(compute.WithFaults(faults))
// and on failure report the seed and faults.Injected().
}
Faithfulness
Each stand-in follows the real API's requests, response shapes and error codes for the
methods it covers, and is tested through the official Go client library. Where
it simplifies, the package documentation says so. For Compute Engine:
- Long-running operations finish before the call that started them returns.
- A call that repeats an earlier call's
requestId in the same project, for the
same method, gets that call's operation back without taking effect again, as
the real API does for retries. The server keeps the last 10,000 operations.
- List filters support
field = value and field != value comparisons on
name, description, status, address, addressType and labels, joined by AND or
in parentheses. Lists are ordered by name. Anything else, such as a wildcard
or orderBy=creationTimestamp desc, is rejected with 400 rather than ignored.
- IP addresses come from 100.64.0.0/10 (external) and 10.0.0.0/8 (internal),
never from routable ranges. An external address is unique across every
project and region; an internal one is unique within its region, which stands
in for its subnetwork.
- A fault rule's
Resource for an insert is the resource being created, so a
rule can target the insert of one name.
For Amazon EC2:
- Signatures are not checked. The region comes from the SigV4 credential
scope, and each region keeps its own resources.
- Subnet and security group IDs are not validated, there are no VPCs, and
instances are not modeled: a test marks an interface attached to an instance
ID of its choosing.
- A
CreateNetworkInterface that repeats a ClientToken with the same
parameters gets the interface it first created, as the real API does for
retries; with other parameters it fails with IdempotentParameterMismatch.
- Filters take EC2's
* and ? wildcards. Filter names and parameters the
stand-in does not support are rejected with 400 rather than ignored.
- Public IP addresses come from 100.64.0.0/10 and private ones from
10.0.0.0/8. There is no address quota unless the test sets one.
- EC2 has no long-running operations, so a failed operation fails the call
before it takes effect. A fault's reason is the EC2 error code it answers
with, and the generic ones become
InternalError, Unavailable and
RequestLimitExceeded. A fault rule's Resource is the region and ID, such
as us-east-1/eipalloc-…, and for a create, the resource being created.
Requirements
Go 1.27 or later. The module requires nothing beyond the standard library; the
tests that use the official client libraries live in the separate sdktest
module.
Contributing
See CONTRIBUTING.md. Security issues: SECURITY.md.
License
Apache 2.0.