Besides plain CRUD, CloudEmu reproduces a number of cloud behaviors so integration tests can check end-to-end logic without deploying anything. This page describes them.
When you launch an instance with RunInstances, the compute mock pushes 5 metrics to the provider's monitoring service. The provider factory sets this up by connecting compute to monitoring with SetMonitoring().
Each instance gets 5 metrics, each with 5 backfill datapoints at 1-minute intervals from launch time:
| Provider | Namespace | Metrics | Dimension Key |
|---|---|---|---|
| AWS | AWS/EC2 |
CPUUtilization, NetworkIn, NetworkOut, DiskReadOps, DiskWriteOps | InstanceId |
| Azure | Microsoft.Compute/virtualMachines |
Percentage CPU, Network In Total, Network Out Total, Disk Read Operations/Sec, Disk Write Operations/Sec | resourceId |
| GCP | compute.googleapis.com |
instance/cpu/utilization, instance/network/received_bytes_count, instance/network/sent_bytes_count, instance/disk/read_ops_count, instance/disk/write_ops_count | instance_id |
VM lifecycle operations also emit metrics, via emitLifecycleMetrics():
| Operation | Values |
|---|---|
StartInstances |
Running values (CPU=25, Network=1024/512, Disk=100/50; GCP CPU=0.25) |
StopInstances |
Zero values (all 0.0) |
RebootInstances |
Running values |
TerminateInstances |
Zero values |
Each lifecycle call emits 1 datapoint per metric at Clock.Now(). Alarms can then react to state changes. For example, a "low CPU" alarm fires when a VM is stopped.
AWS/EC2 datapoints carry the units real EC2 publishes: CPUUtilization is Percent, NetworkIn/NetworkOut are Bytes, and DiskReadOps/DiskWriteOps are Count (visible as Unit on get-metric-statistics).
Besides compute, 9 other services per provider push metrics to monitoring: Storage, Database, Serverless, Message Queue, Cache, Logging, Notification, Container Registry, and Event Bus.
On AWS, these follow the real CloudWatch taxonomy (namespace, metric name, dimensions, unit). Notable examples:
| Service | Namespace | Metrics | Dimensions |
|---|---|---|---|
| DynamoDB | AWS/DynamoDB |
ConsumedRead/WriteCapacityUnits (Count); SuccessfulRequestLatency (Milliseconds); ReturnedItemCount (Count, Query/Scan) | TableName; latency and item count on TableName+Operation |
| Lambda | AWS/Lambda |
Invocations, Errors, Throttles, ConcurrentExecutions (Count); Duration (Milliseconds) | FunctionName |
| ECR | AWS/ECR |
RepositoryPullCount (Count) — the only metric real ECR publishes; pushes emit nothing | RepositoryName |
| Kinesis | AWS/Kinesis |
IncomingBytes/Records, PutRecord.*, PutRecords.*, GetRecords.* (Bytes/Count/Milliseconds) | StreamName |
| Step Functions | AWS/States |
ExecutionsStarted/Succeeded/Failed/Aborted/TimedOut (Count); ExecutionTime (Milliseconds) | StateMachineArn |
| API Gateway | AWS/ApiGateway |
Count, 4XXError, 5XXError (Count); Latency, IntegrationLatency (Milliseconds) | ApiName, and ApiName+Stage |
| Athena | AWS/Athena |
TotalExecutionTime, EngineExecutionTime (Milliseconds); ProcessedBytes (Bytes, DML) — only for workgroups with PublishCloudWatchMetricsEnabled |
QueryState+QueryType+WorkGroup |
Each call to PutMetricData makes the monitoring mock evaluate every alarm that matches the affected namespace and metric name. The logic is in evaluateAlarms() in each monitoring mock.
- For each metric datum pushed, find alarms matching the namespace + metric name + dimensions.
- Collect datapoints within the evaluation window:
Period * EvaluationPeriodsseconds. - Compute the statistic over those datapoints:
Average: mean of all valuesSum: sum of all valuesMinimum: smallest valueMaximum: largest valueSampleCount: number of datapoints
- Compare against the alarm's threshold using the configured operator.
- Set the alarm state to
"ALARM"or"OK".
GreaterThanThresholdLessThanThresholdGreaterThanOrEqualToThresholdLessThanOrEqualToThreshold
An AWS alarm can compare its metric with ANOMALY_DETECTION_BAND(m1, k) instead of a fixed threshold. It names the band with ThresholdMetricId and uses LessThanLowerOrGreaterThanUpperThreshold, LessThanLowerThreshold or GreaterThanUpperThreshold. GetMetricData returns a band as two rows with the same Id, the lower edge first.
AWS computes the band with a machine learning model. CloudEmu uses an approximation:
- For each point, the band is the mean plus or minus
kstandard deviations of the metric's points in the two weeks before it.kdefaults to 2. - A point needs at least 3 earlier points. Until then it has no band, and the alarm treats that period as missing data.
- Points whose period overlaps one of the detector's
ExcludedTimeRangesare left out. - There is no seasonality and no trend.
MetricTimezoneandPeriodicSpikesare stored and returned but do not change the band.
PutAnomalyDetector, DescribeAnomalyDetectors and DeleteAnomalyDetector manage detectors. Evaluating an anomaly alarm creates its detector if it is missing, as on AWS. A detector reports PENDING_TRAINING until its metric has data in 3 one-minute periods, then TRAINED. When its data ages out of the two-week window it reports TRAINED_INSUFFICIENT_DATA.
Alarms have three kinds of action channels:
AlarmActions: notification channel IDs to notify when the state changes toALARMOKActions: channel IDs to notify when the state changes toOKInsufficientDataActions: channel IDs to notify onINSUFFICIENT_DATA
Every state change is recorded in the alarm history, which you can read with GetAlarmHistory(). Each entry has the alarm name, timestamp, old state, new state, and a reason string.
CheckPermission(principal, action, resource) evaluates real JSON policy documents against a request.
- Look up the principal (user or role) and collect all attached policy ARNs.
- For users, also collect policies attached to the user's groups.
- Parse each policy's JSON document into statements.
- For each statement, check whether the action and resource match using
wildcardMatch(). - Apply standard IAM evaluation logic:
- An explicit
Denyalways overridesAllow. - If no statement explicitly allows the action, the result is deny.
wildcardMatch()supports*(any sequence) and?(a single character).
- An explicit
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": ["arn:aws:s3:::my-bucket/*"]
},
{
"Effect": "Deny",
"Action": ["s3:DeleteObject"],
"Resource": ["*"]
}
]
}With this policy attached, CheckPermission("user1", "s3:GetObject", "arn:aws:s3:::my-bucket/file.txt") returns true and CheckPermission("user1", "s3:DeleteObject", "arn:aws:s3:::my-bucket/file.txt") returns false.
FIFO queues drop duplicate messages sent within a 5-minute deduplication window.
- Each FIFO queue keeps a
deduplicationIndex map[string]time.Timerecording when eachDeduplicationIDwas last seen. - When
SendMessageis called with aDeduplicationID:- If the same ID was seen in the last 5 minutes, the call returns the existing
MessageIDand doesn't create a new message. - If the ID is new, or was last seen more than 5 minutes ago, a new message is created and the index is updated.
- If the same ID was seen in the last 5 minutes, the call returns the existing
SentAt time.Timeon message structs records when each message was sent.
This behavior matches the real AWS SQS, Azure Service Bus, and GCP Pub/Sub FIFO semantics.
Use config.FakeClock to control time in dedup tests:
clock := config.NewFakeClock(time.Now())
aws := cloudemu.NewAWS(config.WithClock(clock))
// First send: creates message
aws.SQS.SendMessage(ctx, input)
// Second send within 5 minutes: returns same MessageID
aws.SQS.SendMessage(ctx, input)
// Advance past dedup window
clock.Advance(6 * time.Minute)
// Third send: creates new message
aws.SQS.SendMessage(ctx, input)You can add GSIs to a table with a different partition key and an optional sort key. A query targets an index by name through QueryInput.IndexName.
| Operation | Description |
|---|---|
CreateIndex |
Add a GSI to an existing table |
DeleteIndex |
Remove a GSI |
DescribeIndex |
Get GSI status and key schema |
ListIndexes |
List all GSIs on a table |
The compareValues(a, b string) helper in each database mock tries strconv.ParseFloat on both values. If both parse as numbers, it compares them numerically; otherwise it compares them as strings. All comparison operators in scan filters and query sort conditions use it.
The database drivers evaluate the real expression grammars, not a reduced subset. The expression strings a client sends are tokenized, parsed and evaluated with type-aware semantics:
- DynamoDB:
KeyConditionExpression(=/</<=/>/>=,BETWEEN,begins_with),FilterExpression/ConditionExpression(boolean operators,IN,BETWEEN,attribute_exists/attribute_type/begins_with/contains/size),ProjectionExpression, andUpdateExpression(SETwith arithmetic,if_not_exists,list_append;REMOVE;ADD;DELETE), including theSS/NS/BSset types. - Firestore: structured queries with all field operators (
IN/NOT_IN/ARRAY_CONTAINS/ARRAY_CONTAINS_ANY),AND/ORcomposite filters, unaryIS_NULL/IS_NOT_NULL,orderBy,offset,startAt/endAtcursors, and field projection. - Cosmos DB: Cosmos SQL (
SELECT/WHERE/ORDER BY/OFFSET-LIMIT,DISTINCT,TOP, projections includingSELECT VALUE, andCOUNT/SUM/AVG/MIN/MAXaggregates).
The older driver-level ScanFilter/SortOp operators (=, !=, <, >,
<=, >=, CONTAINS, BEGINS_WITH, BETWEEN) are still there for callers
of the Go API.
A table can have TTL on one attribute. The TTL configuration names an AttributeName that holds a Unix timestamp. Items past their TTL can be found and cleaned up.
Tables can enable streams that capture change events (INSERT, MODIFY, REMOVE). Each StreamRecord has the event type, keys, old image, new image, and a sequence number. The stream view type controls what is captured: NEW_IMAGE, OLD_IMAGE, NEW_AND_OLD_IMAGES, or KEYS_ONLY.
TransactWriteItems applies a set of puts and deletes atomically: either all succeed or all fail. This corresponds to DynamoDB's TransactWriteItems, Cosmos DB's transactional batch, and Firestore's transactions.
Message queues support dead-letter queue (DLQ) configuration. When you create a queue, you can pass a DeadLetterConfig with:
TargetQueueURL: the URL of the DLQMaxReceiveCount: after this many receives without a delete, the message moves to the DLQ
Use this to test poison-message handling and retry exhaustion.
// Create the DLQ first
dlq, _ := aws.SQS.CreateQueue(ctx, driver.QueueConfig{Name: "my-dlq"})
// Create the main queue with DLQ config
aws.SQS.CreateQueue(ctx, driver.QueueConfig{
Name: "my-queue",
DeadLetterQueue: &driver.DeadLetterConfig{
TargetQueueURL: dlq.URL,
MaxReceiveCount: 3,
},
})CloudEmu models cost in two ways: a per-operation tracker (metered API usage) and a resource-inventory estimate (what the resources that currently exist would cost per month). Both live in services/cost and use the rate tables in services/pricing.
cost.Tracker estimates the cost of metered cloud operations. Its default per-operation rates are based on approximate real cloud prices.
| Operation | Rate |
|---|---|
compute:RunInstances |
$0.0116/instance-hour |
storage:PutObject |
$0.000005 |
storage:GetObject |
$0.0000004 |
database:PutItem |
$0.00000125 |
database:GetItem |
$0.00000025 |
serverless:Invoke |
$0.0000002 |
messagequeue:SendMessage |
$0.0000004 |
monitoring:PutMetricData |
$0.00001 |
loadbalancer:CreateLoadBalancer |
$0.0225/hour |
tracker := cost.New()
// Record operations
tracker.Record("storage", "PutObject", 100)
tracker.Record("compute", "RunInstances", 2)
// Query costs
total := tracker.TotalCost() // total across all operations
byService := tracker.CostByService() // map[string]float64
byOp := tracker.CostByOperation() // map[string]float64
all := tracker.AllCosts() // []ServiceCost with full detail
// Override a rate
tracker.SetRate("compute", "RunInstances", 0.0464) // m5.xlarge pricing
// Reset
tracker.Reset()services/cost can also build a bill from the resources that exist, instead of from metered calls. cost.Estimate(ctx, inv) walks a provider's resource inventory and emits a cost.Line per resource (service, resource type, SKU/size, region, monthly rate). cost.ServiceMonthly(...) sums the lines by service. The per-resource rates come from services/pricing.Monthly(provider, service, resourceType, sku, region, props). cost.Commitment (with the Commitments registry) models reservations and savings plans and computes Coverage and Utilization over a time window, in the same shape real cost tools report.
The same cost model is served through each provider's native billing APIs, so real FinOps SDKs and CLIs work against the emulator:
| Provider | Handlers | Native surface |
|---|---|---|
| AWS | server/aws/costexplorer, server/aws/savingsplans, server/aws/servicequotas |
Cost Explorer (GetCostAndUsage, …), Savings Plans, Service Quotas |
| Azure | server/azure/costmanagement |
Cost Management (query/usage) |
| GCP | server/gcp/cloudbilling |
Cloud Billing (billing accounts, project billing info) |
The portable API layer can wrap every driver operation with five optional behaviors. You turn them on per service instance with functional options.
Records every API call with the service name, operation, input, output, error, and duration. Use it for assertions like "PutObject was called exactly twice."
Records calls_total (counter), call_duration (histogram), and errors_total (counter) for every operation, labeled by service and operation name.
A token bucket rate limiter. When the bucket is empty, operations return a Throttled error without calling the underlying driver.
Inject errors into specific service/operation pairs using one of these policies:
Always: fail every callNthCall(n): fail every Nth callProbabilistic(p): fail with probability p (0.0-1.0)Countdown(n): fail the first n calls, then succeed
Adds a fixed delay to every operation to simulate network latency.
import (
"time"
"errors"
"github.com/stackshy/cloudemu/v2/services/storage"
"github.com/stackshy/cloudemu/v2/features/recorder"
"github.com/stackshy/cloudemu/v2/features/metrics"
"github.com/stackshy/cloudemu/v2/features/ratelimit"
"github.com/stackshy/cloudemu/v2/features/inject"
cerrors "github.com/stackshy/cloudemu/v2/errors"
)
rec := recorder.New()
col := metrics.NewCollector()
lim := ratelimit.New(100, 10, nil) // 100 req/s, burst 10
inj := inject.NewInjector()
// Fail every 5th GetObject call with a Throttled error
inj.Set("storage", "GetObject",
cerrors.New(cerrors.Throttled, "simulated throttle"),
inject.NewNthCall(5),
)
bucket := storage.NewBucket(awsProvider.S3,
storage.WithRecorder(rec),
storage.WithMetrics(col),
storage.WithRateLimiter(lim),
storage.WithErrorInjection(inj),
storage.WithLatency(5 * time.Millisecond),
)
// Use bucket normally; all cross-cutting concerns are applied
bucket.PutObject(ctx, "my-bucket", "key", data, "text/plain", nil)
// Assert calls were recorded
calls := rec.CallsFor("storage", "PutObject")
count := rec.CallCountFor("storage", "PutObject")
// Check metrics
allMetrics := col.All()Every time-dependent feature in CloudEmu uses the config.Clock interface instead of calling time.Now() directly. Tests can pass a config.FakeClock to make timing fully deterministic.
type Clock interface {
Now() time.Time
Since(t time.Time) time.Duration
After(d time.Duration) <-chan time.Time
}// Create a fake clock set to a specific time
clock := config.NewFakeClock(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC))
// Create providers with the fake clock
aws := cloudemu.NewAWS(config.WithClock(clock))
// Operations use clock.Now() for timestamps
aws.EC2.RunInstances(ctx, config, 1)
// Advance time to test time-dependent behavior
clock.Advance(5 * time.Minute)
// Set to a specific time
clock.Set(time.Date(2025, 1, 2, 0, 0, 0, 0, time.UTC))- FIFO deduplication: the 5-minute window is checked against
clock.Now(). Advance the clock past 5 minutes to test expiry. - Alarm evaluation: metric timestamps and evaluation windows use the clock, so you control when alarms change state.
- Auto-metrics: backfill datapoints are generated at 1-minute intervals from
clock.Now(), so their timestamps are predictable. - TTL evaluation: database TTL checks compare item timestamps against the clock.
- Resource timestamps:
CreatedAt,LastModified, and similar fields all use the clock.
services/resourcediscovery/ is a cross-service inventory engine. It walks every service driver a provider holds and returns one normalized view of what exists. Like features/topology/, it sits beside the portable API: it holds no state, is built from driver interfaces, and only answers queries.
Three SDK-compat handlers that speak the real cloud inventory APIs are built on it: AWS Resource Explorer 2 + Resource Groups Tagging API, Azure Resource Graph, and GCP Cloud Asset Inventory. A tag set through any one of them is visible right away through the others, and through the engine's own SearchByTag.
Every provider factory wires the engine for you:
aws := cloudemu.NewAWS(config.WithAccountID("123456789012"), config.WithRegion("us-west-2"))
all, _ := aws.ResourceDiscovery.ListAll(ctx)
// returns Resource{Provider, Service, Type, ID, ARN, Region, Tags, CreatedAt}
// for every bucket, instance, VPC, subnet, security group, table, and functionAzure and GCP providers have the same field (azure.ResourceDiscovery, gcp.ResourceDiscovery). The engine reads from the Compute, Networking, Storage, Database, Serverless, Databricks, Kubernetes, Relational Database, Secrets, Container Registry, Message Queue, Notification, DNS, Logging, Cache, Load Balancer, Monitoring, and IAM drivers. There is also a generic Extra hook for services with no shared driver (ML/GenAI). Nil fields are skipped, so partial test setups work.
These resources appear under their cloud's inventory type strings: managed relational servers (AWS RDS including DB proxies, Redshift, Azure SQL, the MySQL/PostgreSQL Flexible Servers, and Cloud SQL), compute snapshots, networking sub-types (NAT gateways, internet gateways, VPC peering connections, route tables), secrets, container repositories, queues, topics, DNS zones, log groups, cache clusters, load balancers, metric alarms, IAM users/roles/policies/groups, and ML resources (SageMaker models/endpoints/notebooks, Vertex AI endpoints/datasets).
| Operation | Purpose |
|---|---|
ListAll(ctx) |
Every resource the provider currently holds |
List(ctx, Query) |
Filter by Services, Type, Region, and Tags (any-of for Services; AND across all non-empty fields) |
SearchByTag(ctx, key, value) |
Every resource whose Tags[key] == value |
GetTagKeys(ctx) |
Distinct tag keys across the inventory |
GetTagValues(ctx, key) |
Distinct values for a key |
TagResourceByARN(ctx, arn, tags) |
Apply tags to a resource addressed by canonical ARN/URN |
UntagResourceByARN(ctx, arn, keys) |
Remove tag keys from a resource addressed by canonical ARN/URN |
The Resource struct is the same for every cloud:
type Resource struct {
Provider string // "aws" | "azure" | "gcp"
Service string // "compute" | "storage" | "networking" | "database" | "serverless"
Type string // e.g. "instance", "bucket", "vpc", "table", "function"
ID string
ARN string // AWS ARN, Azure resource ID, or GCP //-prefixed URN
Region string
Tags map[string]string
CreatedAt time.Time
}The engine backs three handlers, each registered on its provider's SDK-compat server. They all read from (and write tags through) the same engine, so which one you use depends only on the SDK your code already uses.
| Cloud | Handler | What real SDK clients see |
|---|---|---|
| AWS | server/aws/resourceexplorer2 + server/aws/resourcegroupstaggingapi |
resourceexplorer2.Search, resourcegroupstaggingapi.GetResources/TagResources/UntagResources/GetTagKeys/GetTagValues |
| Azure | server/azure/resourcegraph |
armresourcegraph.Resources: KQL-shaped query over the unified inventory |
| GCP | server/gcp/cloudasset |
cloudasset.SearchAllResources, assets.List, ExportAssets, Feeds CRUD, Operations.Get |
See services.md: Resource Discovery for the per-handler operation list and sdk-server.md for the wire protocols.
By default CloudEmu is purely in-memory: every driver keeps state in
memstore and returns synthetic responses, and no external processes run. If
you want clients to run real workloads (real SQL, real Redis commands, real
function code) against the emulator, you can back drivers with an opt-in real
engine. The in-memory default doesn't change; a nil engine means "stay
in-memory".
config/engine.go defines six engine seams. Each is set with a
config.With<X>Engine(...) option:
| Capability | Option | Backed by |
|---|---|---|
| Relational database | WithDatabaseEngine |
real Postgres / MySQL |
| Cache | WithCacheEngine |
real Redis |
| Functions | WithFunctionEngine |
real code execution (subprocess / Docker) |
| Compute | WithComputeEngine |
Docker containers as VMs |
| Containers | WithContainerEngine |
Docker containers |
| Object storage | WithStorageEngine |
filesystem-backed object bytes |
The engine implementations are in separate Go modules, so their large
dependencies stay out of the core cloudemu module:
contrib/realengine(no Docker): real Postgres viaembedded-postgres, real Redis viaminiredis, real function execution via the host'spython3/node, and filesystem-backed object storage.contrib/dockerengine(Docker required): MySQL, Docker-backed compute and containers, and Azure Functions. Tests are skipped when Docker isn't available.
import (
cloudemu "github.com/stackshy/cloudemu/v2"
"github.com/stackshy/cloudemu/v2/config"
"github.com/stackshy/cloudemu/v2/contrib/realengine/postgres"
)
pg, _ := postgres.New() // starts a real Postgres
aws := cloudemu.NewAWS(config.WithDatabaseEngine(pg))
defer aws.Close() // Provider.Close() tears down every wired engineProvider.Close() calls Options.EngineClosers(), so every engine that
implements io.Closer is shut down when the provider is closed.
For the standalone server, the cloudemu-server binary in contrib/server
bundles the engines and turns them on with flags (--db, --cache, --functions,
--compute, --containers, --all-real). See
standalone-server.md: Real engines.
State lives in memstore, so by default nothing survives: it is lost when the
process exits, and /_cloudemu/reset empties it. If you want state to survive
a restart, the persist package saves the whole emulator as one JSON document
and restores it into a fresh instance. It is opt-in, and CloudEmu doesn't write
to disk unless you ask it to.
Two properties matter:
- It covers everything. Every stateful service (one holding an in-memory
memstore.Store) in AWS, Azure, GCP and OCI is captured. A completeness test (persist/completeness_test.go) fails the build if someone adds a stateful service without persistence support. - It keeps identities. Resource IDs and the ID references between resources
are written as-is, so clients can't tell a restored instance from the
original: a restored EC2 instance keeps its
i-…ID.
Services are found by reflection (internal/snapshot.Discover, exposed per
provider as SnapshotServices()), and each mock saves and restores itself via
the internal/snapshot.Snapshottable interface. The file on disk is a single
readable JSON document covering every provider, which diffs cleanly in version
control (schema version 5).
import (
cloudemu "github.com/stackshy/cloudemu/v2"
"github.com/stackshy/cloudemu/v2/persist"
)
aws := cloudemu.NewAWS()
targets := map[string]persist.Services{"aws": aws.SnapshotServices()}
snap, _ := persist.ExportAll(ctx, targets, persist.Options{IncludeAssets: true})
_ = snap.WriteFile("state.json")
fresh := cloudemu.NewAWS()
loaded, _ := persist.ReadFile("state.json")
_ = persist.RestoreAll(ctx, &loaded, map[string]persist.Services{"aws": fresh.SnapshotServices()})Options{IncludeAssets: false} (the default) gives a metadata-only snapshot
without large object bodies. On the standalone server the same feature is
available as cloudemu serve --persist, the cloudemu snapshot save/load commands, and the
GET/POST /_cloudemu/snapshot endpoint. See persistence.md.
features/vcr records the wire traffic through the standalone server into a
cassette and replays it later, so a recorded session can be served again with no
backend. It wraps each provider handler as HTTP middleware:
- Record (
vcr.ModeRecord) passes requests to the in-memory handler and appends each request/response pair to the cassette, which is written to disk on shutdown. - Replay (
vcr.ModeReplay) serves the matching recorded responses. In strict mode (the default), a request with no recorded match returns501instead of falling through, so a replay reproduces exactly what was recorded.
On the server, use --vcr record|replay, --vcr-cassette <path>, and
--vcr-strict (see standalone-server.md). In Go,
vcr.New(vcr.Options{Mode, CassettePath, Strict, Clock}) returns a *VCR. Its
Wrap(next, provider) method returns the middleware and Flush() writes the cassette.
features/timetravel is a registry of named snapshots built on the persistence
capture/restore functions. It lets you save, restore and branch the state of a
running emulator:
| Operation | Effect |
|---|---|
Save(name) |
capture current whole-emulator state under a name |
Rewind(name) |
restore a saved state, discarding everything since |
Fork(from, to) |
copy a saved state to a new name (branch off a checkpoint) |
Delete(name) / List() |
drop a saved state / enumerate them |
The standalone server exposes these on the admin control plane as
POST /_cloudemu/snapshot/{name} (save), DELETE …/{name},
POST …/{name}/rewind, and POST …/{from}/fork/{to}. See
persistence.md and
standalone-server.md.
features/quota is a per-service quota registry. It holds default and overridden
limits per (serviceCode, quotaCode), records quota-increase requests
(RequestIncrease returns a tracked ChangeRequest), and keeps their history.
The AWS Service Quotas SDK-compat handler (server/aws/servicequotas) is built on it.