chore: cleanup code comments

This commit is contained in:
Julian Sommer
2026-08-04 14:19:58 +10:00
parent 43975b01e6
commit 2e09461080
152 changed files with 367 additions and 385 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ When the report format changes intentionally, regenerate the snapshots:
go test ./.github/actions/e2e-analyze/ -update
```
## Per-failure bundles (Stage 2)
## Per-failure bundles
After parsing, the action materialises a bundle directory per failure
under `<log-dir>/analysis/NN-<TestName>/`. The report links to it from
+3 -3
View File
@@ -1,6 +1,6 @@
// Package main implements the e2e-analyze GitHub Action.
//
// Stage 1: cluster failures from go-junit-report's junit-*.xml by error signature,
// Cluster failures from go-junit-report's junit-*.xml by error signature,
// pick the earliest non-cascade failure per suite as the likely root cause,
// and render a human-readable report.
//
@@ -80,7 +80,7 @@ type Failure struct {
// slicing a plausible-looking window around the wrong minute.
SuiteSpan time.Duration
// BundlePath is the relative path (from log-dir) to the per-failure
// bundle written by Stage 2's WriteBundles. Empty when bundles were
// bundle written by WriteBundles. Empty when bundles were
// not generated (e.g. unit tests that call Render directly).
BundlePath string
}
@@ -552,7 +552,7 @@ func ParseFile(path string, data []byte) (SuiteReport, error) {
return rep, nil
}
// Render writes the markdown report (Stage 1).
// Render writes the markdown report.
func Render(r Report) string {
var b strings.Builder
fmt.Fprintf(&b, "## %s\n\n", r.Title)
+3 -3
View File
@@ -17,9 +17,9 @@ import (
)
// bundlePad is the per-failure window padding either side of the
// [StartAt, StartAt+Duration] testcase span. Five seconds matches the
// plan doc: long enough to catch the daemon's pre-test setup and
// post-test cleanup chatter, short enough to keep the slice focused.
// [StartAt, StartAt+Duration] testcase span. Five seconds catches the
// daemon's pre-test setup and post-test cleanup chatter while keeping
// the slice focused.
const bundlePad = 5 * time.Second
// nameSanitiseRe replaces characters unsafe in directory names with `_`.
+1 -1
View File
@@ -45,7 +45,7 @@ func main() {
report.Suites = append(report.Suites, sr)
}
// Stage 2: prefer authoritative per-suite start times written by the
// Prefer authoritative per-suite start times written by the
// workflow over junit-report's conversion-time `timestamp` attribute,
// then materialise per-failure bundles so Render can link them.
if logDir != "" {
+1 -1
View File
@@ -1,6 +1,6 @@
package main
// Stage 2: per-failure log slicers used by bundle.go to materialise time-window /
// Per-failure log slicers used by bundle.go to materialise time-window /
// testcase-window log excerpts next to each named failure.
import (
+2 -3
View File
@@ -1,8 +1,7 @@
name: E2E DDIL
# Phase 1 trigger: workflow_dispatch only. Nightly cron + on-main-merge
# land in mulga-siv-5.8 once ≥4 scenarios are asserting. See
# docs/development/improvements/ddil-e2e-test-harness.md §4 / §Dependencies.
# This workflow is manually triggered while the DDIL scenarios are being
# validated.
on:
workflow_dispatch:
inputs:
+2 -4
View File
@@ -1,7 +1,5 @@
# E2E (per-suite blocks) — EXPERIMENTAL, workflow_dispatch only.
#
# Restructures the looped e2e.yml into env-grouped job blocks so each suite
# surfaces as its own node in the Actions graph (mulga-siv-283):
# E2E per-suite blocks. Each suite appears as its own node in the Actions
# graph:
#
# single_provision ─┬─ single_suite (matrix: the single-node-eligible suites)
# └─ single_teardown (if: always)
+3 -5
View File
@@ -1,6 +1,6 @@
# GPU E2E — drives wattle's persistent, shared-host Spinifex stack over SSH to
# exercise GPU passthrough (raw EC2, ECS task GPU exposure, EKS pod GPU
# exposure). See docs/development/feature/wattle-deploy-and-ci.md (Part B).
# exposure).
#
# ---------------------------------------------------------------------------
# ONE-TIME HOST-SIDE SETUP — NOT part of this repo, a human must do this:
@@ -32,7 +32,6 @@
# to skip cleanly (job succeeds, all work steps skipped) instead of
# queuing behind manual work indefinitely.
#
# Runbook: docs/development/feature/wattle-deploy-and-ci.md (Part B).
# ---------------------------------------------------------------------------
name: GPU E2E
@@ -236,9 +235,8 @@ jobs:
# (SPINIFEX_E2E=1 SPINIFEX_MODE=single, ./<suite>.test -test.v
# -test.timeout=NNm), plus SPINIFEX_WAN_IP: wattle's ~24 bridges make
# net.InterfaceAddrs() enumeration pick a co-tenant bridge instead of
# the advertised gateway (mulga-7suds), so the override is required
# here (proper fix tracked separately: tests/e2e/harness/env.go should
# prefer spinifex.toml's advertised bridge).
# the advertised gateway, so the override is required here. The harness
# should prefer spinifex.toml's advertised bridge.
- name: Run GPU e2e suite on wattle
id: e2e
if: steps.gate.outputs.paused != 'true'
+2 -2
View File
@@ -8,8 +8,8 @@
# every PR; adding a second-repo checkout there would make every spinifex PR
# depend on mulga main being healthy and pay that checkout cost on every run,
# for one storage-accounting oracle test. Mirrors e2e.yml's push-only trigger
# (mulga-siv-283 precedent): this never gates a PR before merge, matching how
# e2e.yml's own "front gate" job already works.
# This never gates a PR before merge, matching how e2e.yml's own
# "front gate" job works.
name: Segscan Storage Oracle
on:
+3 -3
View File
@@ -70,8 +70,8 @@ func TestRunTask_PullRunReportsRunning(t *testing.T) {
}
}
// TestRunTask_GPUCarriedToRunSpec covers mulga-11opz (Epic C task C1): the
// AssignContainer's GPU count reaches the runtime RunSpec unchanged; a
// TestRunTask_GPUCarriedToRunSpec verifies that the AssignContainer's GPU count
// reaches the runtime RunSpec unchanged; a
// container with no GPU request keeps RunSpec.GPU at zero (regression).
func TestRunTask_GPUCarriedToRunSpec(t *testing.T) {
cp := &fakeCP{}
@@ -534,7 +534,7 @@ func (sentinelResolver) Authorize(context.Context, string) (string, string, stri
return "", "", "", nil
}
// TestPullResolver covers siv-459: an assign with no execution role (or no
// TestPullResolver verifies that an assign with no execution role (or no
// credential endpoint) uses the instance resolver; with both, a distinct
// exec-role resolver is built.
func TestPullResolver(t *testing.T) {
+2 -2
View File
@@ -24,7 +24,7 @@ import (
// stays engine-agnostic.
type engineOps interface {
// Rotates the master role's password live. Never persisted anywhere in the
// guest: it exists only for the length of this call (D8).
// guest: it exists only for the length of this call.
SetPassword(ctx context.Context, username, password string) error
// Installs the resolved parameter set and reloads. Returns the settings the
// engine accepted but will not honour until it restarts.
@@ -180,7 +180,7 @@ ALTER ROLE :"master" WITH LOGIN PASSWORD :'password';
// The include the resolved parameter set is rendered to, and the copy of the
// last one the engine accepted. Both live beside the data rather than in /etc,
// matching rds-init: a class change boots a fresh root volume, which would
// otherwise revert them (D9).
// otherwise revert them when a fresh root volume is used.
const (
parametersFileName = "10-rds-parameters.conf"
// Deliberately not a .conf name: include_dir globs *.conf, so the rollback
+2 -2
View File
@@ -156,7 +156,7 @@ func newTestEngine(t *testing.T, run commandRunner) *postgresEngine {
return newPostgresEngine(cfg, run, nil)
}
// D8: the password reaches psql through the environment and is re-quoted there,
// The password reaches psql through the environment and is re-quoted there,
// so it is never a shell word or an argv another process can read.
func TestPostgresEngine_SetPasswordKeepsTheSecretOutOfArgv(t *testing.T) {
runner := &recordingRunner{}
@@ -215,7 +215,7 @@ func TestPostgresEngine_SetPasswordRejectsAnIncompleteCommand(t *testing.T) {
}
}
// D21: the apply runs as the cluster superuser under peer auth, so a reserved
// The apply runs as the cluster superuser under peer auth, so a reserved
// name arriving in a command payload would rotate the bootstrap superuser's
// password rather than the customer's own role.
func TestPostgresEngine_SetPasswordRefusesReservedRoles(t *testing.T) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"strings"
)
// The in-guest half of a storage grow (D12). The control plane has already
// The in-guest half of a storage grow. The control plane has already
// grown the block device by the time this runs; all that is left is to put the
// filesystem on the capacity that is now there. Both ext4 and XFS grow while
// mounted, so this needs no ordering against the engine start.
+1 -1
View File
@@ -2529,7 +2529,7 @@ func buildRemoteNodes(allNodes map[string]formation.NodeInfo, localNode, northst
continue
}
// Prefer the peer's advertise IP (off-host dial target); fall back
// to BindIP for pre-siv-8 joiners that didn't send AdvertiseIP.
// to BindIP for joiners that did not send AdvertiseIP.
host := n.AdvertiseIP
if host == "" {
host = n.BindIP
+3 -3
View File
@@ -47,7 +47,7 @@ func TestPrintChecksumError(t *testing.T) {
}
// buildRemoteNodes must prefer AdvertiseIP (off-host dial target) and fall
// back to BindIP when the peer pre-dates siv-8 and didn't send AdvertiseIP.
// back to BindIP when the peer did not send AdvertiseIP.
func TestBuildRemoteNodes_AdvertiseFallback(t *testing.T) {
nodes := map[string]formation.NodeInfo{
"node1": {Name: "node1", BindIP: "10.0.0.1", AdvertiseIP: "203.0.113.1"},
@@ -190,7 +190,7 @@ func TestSpinifexTomlTemplate_GatewayListensOnAllPlanes(t *testing.T) {
assert.Contains(t, content, `host = "10.0.0.3:4432"`, "daemon stays internal")
}
// Empty AdvertiseIP (e.g. loading an existing cluster pre-siv-8) must NOT
// Empty AdvertiseIP (for example when loading an existing cluster) must NOT
// render an empty advertise = "" line — downstream fallback to Host kicks in.
func TestSpinifexTomlTemplate_AdvertiseOmittedWhenEmpty(t *testing.T) {
dir := t.TempDir()
@@ -253,7 +253,7 @@ func TestSpinifexTomlTemplate_NorthstarDomains(t *testing.T) {
}
// Legacy `wan_bridge` TOML key must fail-start vpcd with guidance, not silently
// alias (per mulga-998 D3). Prevents the footgun where operators inherited the
// alias. Prevents the footgun where operators inherited the
// old key pointing at 'br-ext' and got broken DHCP on veth-mode hosts.
func TestCheckLegacyWanBridgeKey_TOMLRejected(t *testing.T) {
dir := t.TempDir()
+2 -2
View File
@@ -784,8 +784,8 @@ var spinifexUIStatusCmd = &cobra.Command{
}
// checkLegacyWanBridgeKey fails vpcd startup if the deprecated `wan_bridge`
// TOML key or `SPINIFEX_VPCD_WAN_BRIDGE` env-var is present. Per D3: no silent
// alias, no auto-rewrite — operator must remove the key before vpcd will start.
// TOML key or `SPINIFEX_VPCD_WAN_BRIDGE` env-var is present. There is no silent
// alias or auto-rewrite; the operator must remove the key before vpcd will start.
func checkLegacyWanBridgeKey(node, cfgFile string) error {
legacyInTOML := viper.IsSet("nodes." + node + ".vpcd.wan_bridge")
legacyInEnv := os.Getenv("SPINIFEX_VPCD_WAN_BRIDGE") != ""
+1 -1
View File
@@ -20,7 +20,7 @@ Seed=deadbeef-dead-beef-dead-beefdeadbeef
RepartOffline=yes
[Content]
# Every image built from this base is a bootable UEFI disk (D11). mkosi
# Every image built from this base is a bootable UEFI disk. mkosi
# installs the kernel below and owns the bootloader/ESP itself, so boot-time
# tweaks are expressed as mkosi settings rather than sed patches against
# extlinux/GRUB. Bootloader= defaults to systemd-boot, the native fit for
-3
View File
@@ -3,9 +3,6 @@
Node installation, dev-environment lifecycle, guest image builds, and verification
tooling for Spinifex.
Repo-level tooling (releases, CI infrastructure, ISO builds, beads) lives in
[`mulga/scripts/`](../../scripts/README.md).
## Install and node setup
| Script | What it does |
+1 -1
View File
@@ -10,7 +10,7 @@ IMAGE_SIZE="4G"
# containerd-openrc ships the OpenRC service for containerd (the containerd apk
# itself has no init script). cni-plugins + the baked bridge conflist ready the
# image for Sprint 4d task networking. curl is used by the first-boot CA hook and
# image for task networking. curl is used by the first-boot CA hook and
# IMDS probes. iproute2 supplies the full `ip` (busybox lacks `ip netns`) the
# agent uses to build awsvpc task netns; udhcpc is busybox built-in. iptables is
# the credential endpoint's DNAT rule (cmd/ecs-agent/credendpoint.go) fronting
+1 -1
View File
@@ -14,7 +14,7 @@ chmod 0755 /etc/init.d/ecs-agent /etc/init.d/ecs-ca-install
# cni-plugins (alpine) installs to /usr/libexec/cni; containerd's CRI plugin
# defaults its bin dir to /opt/cni/bin. Symlink so the baked bridge conflist
# resolves its plugins when Sprint 4d wires task networking.
# resolves its plugins when task networking is configured.
mkdir -p /opt/cni
ln -sf /usr/libexec/cni /opt/cni/bin
+1 -1
View File
@@ -138,7 +138,7 @@ case "${ROLE}" in
# Managed-addon delivery runs only on the primary server: it renders
# staged addon bundles into the K3s auto-deploy dir, which a single
# writer owns. HA multi-server addon delivery is tracked separately
# (mulga-siv-231.7); server-join nodes do not run it.
# Server-join nodes do not run this setup.
svc_enable mulga-eks-addon-sync
svc_start eks-token-webhook
svc_start mulga-eks-k3s-recovery
+2 -2
View File
@@ -398,8 +398,8 @@ if ip link show veth-wan-br >/dev/null 2>&1; then
sudo ip link del veth-wan-br 2>/dev/null || true
fi
# Remove veth persistence units (Fix 1, mulga-998.b). Without this, systemd-networkd
# recreates the veth on next reboot even after a full dev reset.
# Remove veth persistence units. Without this, systemd-networkd recreates the
# veth on next reboot even after a full dev reset.
if [ -e /etc/systemd/network/15-spinifex-veth-wan.netdev ] || \
[ -e /etc/systemd/network/15-spinifex-veth-wan.network ] || \
[ -e /etc/systemd/network/16-spinifex-veth-wan-ovs.network ]; then
+3 -3
View File
@@ -544,7 +544,7 @@ if [ -n "$WAN_BRIDGE" ]; then
# when switching to any non-veth mode. Idempotent — each command uses
# --if-exists / 2>/dev/null to tolerate absence. Without this, the
# veth pair re-materialises on reboot and fights the current mode's
# bridge plumbing (Fix 1, mulga-998.b, per D17).
# Bridge plumbing for the host-to-OVN datapath.
if [ "$WAN_BRIDGE_MODE" != "veth" ]; then
sudo rm -f /etc/systemd/network/14-spinifex-br-wan.netdev \
/etc/systemd/network/15-spinifex-veth-wan.netdev \
@@ -621,7 +621,7 @@ if [ -n "$WAN_BRIDGE" ]; then
# Persist the veth pair across reboot via systemd-networkd. Veths
# are kernel-only and vanish on reboot; without persistence vpcd
# starts with the OVS port pointing at a nonexistent peer and
# silently falls back to direct mode (Fix 1, mulga-998.b).
# silently falls back to direct mode.
#
# networkd's Bridge= directive requires the target bridge to be a
# known NetDev. On ISO-installed nodes the installer writes
@@ -673,7 +673,7 @@ NETWORK
# (enslaved via ovs-vsctl add-port above) but does not flip admin
# state on external ports — that's networkd's job. Without this,
# veth-wan-ovs stays DOWN after reboot, peer goes LOWERLAYERDOWN,
# br-wan loses carrier (Fix 1 follow-up, mulga-998.b).
# br-wan loses carrier.
sudo tee "$VETH_OVS_NETWORK" >/dev/null <<NETWORK
[Match]
Name=veth-wan-ovs
+1 -1
View File
@@ -860,7 +860,7 @@ print_summary() {
echo ""
}
# --- Configure host swap (mulga-siv-251) ---
# --- Configure host swap ---
# Provisions an 8G swapfile and lowers vm.swappiness so spinifex.slice
# (MemorySwapMax=100%) has a backing device for graceful degradation under
# memory pressure. Reverses the historical swap=0 assumption. Idempotent.
+4 -4
View File
@@ -651,7 +651,7 @@ func (rm *ResourceManager) GetResourceStats() (totalVCPU int, totalMemGB float64
totalVCPU = rm.hostVCPU
totalMemGB = rm.hostMemGB
// Fold the capacity-reservation carve-out into the reported reserve figures;
// Phase 1 has no separate status field for it.
// The probe has no separate status field for it.
reservedVCPU = rm.reservedVCPU + rm.reservedCRVCPU
reservedMemGB = rm.reservedMem + rm.reservedCRMem
allocVCPU = rm.allocatedVCPU
@@ -695,10 +695,10 @@ func NewDaemon(cfg *config.ClusterConfig) (*Daemon, error) {
cfg.Nodes[cfg.Node] = nodeCfg
}
// Phase 1: always probe GPU hardware (no side effects, no config required).
// Always probe GPU hardware (no side effects, no config required).
gpuProbe := probeGPU()
// Phase 2: activate GPU passthrough only when the operator has opted in.
// Activate GPU passthrough only when the operator has opted in.
var gpuModels []instancetypes.GPUModel
var gpuMigProfiles []instancetypes.MIGProfileSpec
var gpuMgr *gpu.Manager
@@ -1677,7 +1677,7 @@ func (d *Daemon) startCluster() error {
})
// Bedrock serving-endpoint lifecycle: no reconciler yet (no idle-reclaim or
// health sweep — out of this bead's scope), just the request-driven
// health sweep), just the request-driven
// ensure/describe/list/delete surface RDS-style, constructed synchronously
// since it touches no JetStream KV until its first request.
d.bedrockService = handlers_bedrock.NewService(d.natsConn, d.buildBedrockServiceDeps())
+1 -1
View File
@@ -192,7 +192,7 @@ func eniHotplugErrorCode(err error) string {
}
// publishENIHotplugEvent emits the per-instance NATS event on best-effort
// basis — observers (Phase 4 ecs-agent, future vpcd subscribers) read it
// basis — observers such as the ecs-agent and future vpcd subscribers read it
// for fanout. A publish failure does not roll back the pipeline.
func publishENIHotplugEvent(nc *nats.Conn, subject, instanceID string, payload map[string]any) {
if nc == nil {
@@ -319,7 +319,7 @@ func withShortForwardTimeout(t *testing.T, d time.Duration) {
}
// TestHandleEC2StartStoppedInstance_ForwardTimeoutFallsBackLocally pins
// siv-481: a forward to LastNode that times out (as opposed to an immediate
// A forward to LastNode that times out (as opposed to an immediate
// ErrNoResponders) must still fall back to a local start attempt instead of
// surfacing a bare ServerInternal. The target subscriber below is alive but
// silent, so nats: timeout is the only error the forward can produce — proof
@@ -350,7 +350,7 @@ func TestHandleEC2StartStoppedInstance_ForwardTimeoutFallsBackLocally(t *testing
}
// TestHandleEC2StartStoppedInstance_ForwardTimeoutAfterRemoteClaim_NoDoubleStart
// pins the other half of siv-481: if the forward times out on the caller's
// If the forward times out on the caller's
// side AFTER the original node already won the atomic claim (removed the
// record from shared KV) and kept working, the caller's local fallback must
// not double-start the instance. It should observe the record already gone
+1 -1
View File
@@ -110,7 +110,7 @@ func (d *Daemon) handleAttachVolume(ctx context.Context, msg *nats.Msg, command
// which only matches the API-form name persisted in volume metadata.
// A guest-form name here breaks the immediate post-attach wait loop.
// This diverges intentionally from BlockDeviceMappings, which retain
// the guest path under mulga-599.
// the guest path for in-guest device discovery.
d.respondWithVolumeAttachment(msg, volumeID, command.ID, device, "attached")
}
+3 -3
View File
@@ -73,7 +73,7 @@ type MgmtIPRecord struct {
//
// Allocation is backed by a cluster-wide NATS KV compare-and-swap record
// once BindKV has been called, so nodes sharing the same br-mgmt L2 segment
// never hand out the same address (mulga-f3j2x): every node independently
// never hand out the same address: every node independently
// scanning from .10 was the bug, since br-mgmt is one flat subnet across the
// whole cluster, not a per-host range.
//
@@ -204,7 +204,7 @@ func (a *MgmtIPAllocator) Allocate(instanceID string) (string, error) {
// nextFreeIP scans rangeMin-rangeMax on the /24 rooted at baseIP, skipping
// every IP already present in allocated regardless of which entry's Node it
// belongs to — that is the whole fix for mulga-f3j2x: the range is shared
// belongs to — the range is shared
// across the cluster, not partitioned per host.
func nextFreeIP(baseIP net.IP, rangeMin, rangeMax byte, allocated []MgmtIPEntry) (string, error) {
used := make(map[string]struct{}, len(allocated))
@@ -268,7 +268,7 @@ func (a *MgmtIPAllocator) Release(instanceID string) {
// — once KV is bound (see BindKV) and healthy — CAS-inserts an entry for
// every local VM carrying a MgmtIP, idempotent by instance ID. This never
// removes another node's entries: reclaiming addresses of a dead node is a
// separate concern (tracked apart from mulga-f3j2x).
// separate concern.
//
// Called on daemon startup (startLocal, cache-only since KV isn't bound yet)
// and again after cluster bootstrap (startCluster, once BindKV has run) so
+1 -1
View File
@@ -317,7 +317,7 @@ func TestMgmtIPAllocator_Concurrent(t *testing.T) {
}
}
// --- Cluster-safety coverage (mulga-f3j2x) ---
// --- Cluster-safety coverage ---
// TestMgmtIPAllocator_SharedKV_NoDuplicates is the core regression test:
// two allocators standing in for two nodes, sharing one KV, must never both
+1 -1
View File
@@ -767,7 +767,7 @@ func (d *Daemon) buildDirectBootConfig(instanceID string, input *handlers_elbv2.
// Without virtio_mmio.device params the kernel never discovers the devices
// (auto-kernel-cmdline does not append when -append is specified).
//
// Boot-time perf flags (Phase 2):
// Boot-time performance flags:
// quiet loglevel=3 — suppress printk to ttyS0 (115200 baud serial
// writes dominate boot time; warnings+errors only)
// mitigations=off — skip spectre/meltdown microcode patching at
@@ -682,7 +682,7 @@ func TestRefreshSystemInstanceState_RewritesBlobs(t *testing.T) {
}
// TestRefreshSystemInstanceState_WriteErrorPropagates exercises the
// writeFwCfgBlobs error path (plan §3): a write failure under the tmpfs
// writeFwCfgBlobs error path: a write failure under the tmpfs
// path must surface as a wrapped error so MarkRecoveryFailed gets called.
func TestRefreshSystemInstanceState_WriteErrorPropagates(t *testing.T) {
d, nc, tmpDir := newRefreshTestDaemon(t)
+2 -2
View File
@@ -1551,7 +1551,7 @@ func TestInstanceCleanerAdapter_DeleteVolumes_DeleteOnTermination_False(t *testi
}
// TestInstanceCleanerAdapter_DeleteVolumes_BootVolumeDeletedAfterAttachmentClear
// locks the mulga-1dzx9 fix on the running-instance terminate path: a Boot,
// locks the running-instance terminate path: a Boot,
// DeleteOnTermination root volume left attached (terminateCleanup's
// shutdownAndUnmount runs Unmount, which deliberately never clears a Boot
// volume's AttachedInstance) previously hit DeleteVolume's in-use guard
@@ -3119,7 +3119,7 @@ func TestGetAvailableInstanceTypeInfos_GPUType_ShowCapacity(t *testing.T) {
}
// canAllocate for GPU types is gated by CPU/memory like any other instance
// type. Regression for mulga-jeo02: the whole-GPU early return used to
// type. The whole-GPU early return used to
// bypass this check entirely, letting a launch proceed on a host with no
// memory headroom.
func TestCanAllocate_GPUType_GatedByCPUMemory(t *testing.T) {
+2 -2
View File
@@ -181,7 +181,7 @@ func TestWireLBAgentConfig_NoMgmtRoute(t *testing.T) {
// AdvertiseIP covers the single-node default install (no br-mgmt, AWSGW on
// wildcard): the gateway URL falls back to the node's advertised off-host IP.
// Replaces the siv-6 "empty gateway URL" silent failure.
// Covers the "empty gateway URL" failure.
func TestWireLBAgentConfig_GatewayURL_AdvertiseFallback(t *testing.T) {
cfg := &config.Config{
AdvertiseIP: "192.168.1.21",
@@ -197,7 +197,7 @@ func TestWireLBAgentConfig_GatewayURL_AdvertiseFallback(t *testing.T) {
// When there's no br-mgmt, no AdvertiseIP, no DevNetworking, and AWSGW is on
// the wildcard, the gateway URL must stay empty and the daemon must log an
// error (rather than silently assigning a broken URL like pre-siv-8).
// error rather than silently assigning a broken URL.
func TestWireLBAgentConfig_GatewayURL_NoAdvertiseNoBridge(t *testing.T) {
cfg := &config.Config{
AWSGW: config.AWSGWConfig{Host: "0.0.0.0:9999"},
+1 -1
View File
@@ -113,7 +113,7 @@ func TestTerminateWorkerInstances_OwnerErrorSurfaces(t *testing.T) {
// torn down at stop). The terminate must fall back to the ec2.terminate queue
// group so TerminateStoppedInstance reaps the worker from shared KV — otherwise
// a stopped+wedged node's ENI pins the customer subnet/VPC undeletable and
// DeleteCluster wedges in DELETING (mulga-siv-475).
// DeleteCluster wedges in DELETING.
func TestTerminateWorkerInstances_StoppedFallbackToEC2Terminate(t *testing.T) {
noopTerminateRetrySleep(t)
nc, err := nats.Connect(sharedNATSURL)
+1 -1
View File
@@ -31,7 +31,7 @@ func claimMIGSliceForReconcilerTest(t *testing.T, instanceID string) *gpu.Manage
}
// TestGPUPoolReconciler_FreesOrphanedSlot is the regression test for
// mulga-keptb: a GPU pool entry whose InstanceID names an instance absent
// A GPU pool entry whose InstanceID names an instance absent
// from the running map entirely must be released.
func TestGPUPoolReconciler_FreesOrphanedSlot(t *testing.T) {
mgr := claimMIGSliceForReconcilerTest(t, "i-orphan")
+1 -1
View File
@@ -491,7 +491,7 @@ func (m *JetStreamManager) UpdateClusterShutdown(mutate func(*ClusterShutdownSta
// empty record into existence. Every node sharing the management bridge's
// L2 segment reads and writes the same record, keyed by subnet, which is
// what makes mgmt IP allocation safe across the whole cluster rather than
// just this process (mulga-f3j2x).
// just this process.
func (m *JetStreamManager) UpdateMgmtIPAM(subnet string, mutate func(*MgmtIPRecord), createIfAbsent bool) (*MgmtIPRecord, error) {
if m.clusterKV == nil {
return nil, errors.New("cluster state KV not initialized")
+1 -1
View File
@@ -1645,7 +1645,7 @@ func TestJetStreamManager_BestEffort_NilObserver_NoPanic(t *testing.T) {
// must be detected via a revision conflict and retried, and both the
// injected concurrent write and the retried write must land — mirroring
// UpdateStoppedInstance/UpdateTerminatedInstance's conflict-retry tests,
// applied to the allocator's backing store (mulga-f3j2x).
// applied to the allocator's backing store.
func TestJetStreamManager_UpdateMgmtIPAM_ConflictRetry(t *testing.T) {
nc, err := nats.Connect(sharedJSNATSURL)
require.NoError(t, err)
+6 -6
View File
@@ -14,21 +14,21 @@ const (
phase2ArtifactGateMiB = 50
)
func TestBootGate_Phase1(t *testing.T) {
func TestBootGate_ProbeThreshold(t *testing.T) {
if phase1BootGateMs != 500 {
t.Errorf("Phase 1 boot gate changed: got %d, want 500", phase1BootGateMs)
t.Errorf("probe boot gate changed: got %d, want 500", phase1BootGateMs)
}
if phase1RSSGateMiB != 25 {
t.Errorf("Phase 1 RSS gate changed: got %d, want 25", phase1RSSGateMiB)
t.Errorf("probe RSS gate changed: got %d, want 25", phase1RSSGateMiB)
}
}
func TestBootGate_Phase2(t *testing.T) {
func TestBootGate_ActivationThreshold(t *testing.T) {
if phase2BootGateMs != 300 {
t.Errorf("Phase 2 boot gate changed: got %d, want 300", phase2BootGateMs)
t.Errorf("activation boot gate changed: got %d, want 300", phase2BootGateMs)
}
if phase2ArtifactGateMiB != 50 {
t.Errorf("Phase 2 artifact gate changed: got %d, want 50", phase2ArtifactGateMiB)
t.Errorf("activation artifact gate changed: got %d, want 50", phase2ArtifactGateMiB)
}
}
+2 -2
View File
@@ -419,14 +419,14 @@ func TestCanAllocateLocked_MIGvsWholeGPU(t *testing.T) {
rm.allocatedMem = 32.0
assert.Equal(t, 0, rm.canAllocateLocked(migType, 10), "MIG: second slice rejected when resources exhausted")
// Whole-GPU: regression for mulga-jeo02. With cpu/mem already exhausted by
// Whole-GPU: with cpu/mem already exhausted by
// the MIG slice above, a whole-GPU instance must also be refused instead
// of bypassing the check.
assert.Equal(t, 0, rm.canAllocateLocked(wholeGPUType, 10), "whole-GPU: no longer bypasses the cpu/mem gate")
}
// TestCanAllocateLocked_WholeGPU_MemoryAndSlotGates is the regression test
// for mulga-jeo02: a whole-GPU launch must be refused (allocateForLaunch maps
// a whole-GPU launch must be refused (allocateForLaunch maps
// this to InsufficientInstanceCapacity) when the node lacks the instance's
// memory, must succeed when memory is available, and must be refused up
// front — not just late at gpuManager.Claim — when the GPU pool is exhausted.
+1 -1
View File
@@ -67,7 +67,7 @@ func TestEbsRequestWithTrace_HeaderCarriesTraceparent(t *testing.T) {
// helper viperblockd's ebs.mount/ebs.unmount handlers use) shares the producer
// span's trace ID and names it as its parent, rather than rooting a new trace
// — the exact gap this fix closes for the 81 mount + 81 unmount rooted spans
// the bead's CI trace-analysis run found.
// the trace analysis found.
func TestEbsRequestWithTrace_ProducerConsumerLinked(t *testing.T) {
sr := withRecordedSpans(t)
daemon := createTestDaemon(t, sharedNATSURL)
+1 -1
View File
@@ -103,7 +103,7 @@ func (gw *GatewayConfig) BedrockRuntime_Request(w http.ResponseWriter, r *http.R
return errors.New(awserrors.ErrorServerInternal)
}
// Ochre metering (mulga-vmfng.7.6). RPM is a local, in-memory check —
// Ochre metering. RPM is a local, in-memory check —
// cheap enough to run before the body is even read. The token cap reads
// the stream-fed usage counter, so it goes second; both are no-ops
// (nil-safe on gw.Quota) unless their dimension is explicitly enabled.
@@ -263,7 +263,7 @@ func subscribeEmptyInstanceBuckets(t *testing.T, nc *nats.Conn) {
}
// TestDescribeInstancesChecked_ExplicitIDNotFound_CompleteSweep covers the
// bead's primary criterion: naming a nonexistent instance ID with a
// The primary criterion: naming a nonexistent instance ID with a
// fully-answered fan-out returns InvalidInstanceID.NotFound instead of a
// silent empty list.
func TestDescribeInstancesChecked_ExplicitIDNotFound_CompleteSweep(t *testing.T) {
@@ -346,7 +346,7 @@ func TestDescribeInstancesChecked_ExplicitIDFound_CompleteSweep(t *testing.T) {
}
// TestDescribeInstancesChecked_FilterOnlyNoMatch_ReturnsEmptyNotNotFound
// covers the bead's second criterion: a --filters query that matches nothing
// covers the second criterion: a --filters query that matches nothing
// must still return rc=0 with an empty list, never NotFound (no instance IDs
// were named).
func TestDescribeInstancesChecked_FilterOnlyNoMatch_ReturnsEmptyNotNotFound(t *testing.T) {
@@ -370,7 +370,7 @@ func TestDescribeInstancesChecked_FilterOnlyNoMatch_ReturnsEmptyNotNotFound(t *t
assert.Empty(t, output.Reservations)
}
// TestDescribeInstancesChecked_ExplicitIDNotFound_NodeTimeout is the bead's
// TestDescribeInstancesChecked_ExplicitIDNotFound_NodeTimeout verifies the
// subtle third criterion: a node timing out during the fan-out must not
// produce a false NotFound. Only one of two expected nodes answers, so the
// sweep is provably incomplete and the naive found-set check must be
@@ -45,7 +45,7 @@ func TestRunInstancesInner_TargetedMalformedID(t *testing.T) {
assert.Equal(t, awserrors.ErrorInvalidCapacityReservationIdMalformed, err.Error())
}
// The placement-group + capacity-reservation combination is rejected (Phase 2).
// The placement-group + capacity-reservation combination is rejected.
func TestRunInstancesInner_TargetedWithPlacementGroup(t *testing.T) {
input := crTargetedInput(testCRID)
input.Placement = &ec2.Placement{GroupName: aws.String("pg-cluster")}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
)
// TestClassifyOperation_RouteMatrix pins the plan's OCI request -> ECR action
// -> resource table exactly. Every row here must match the plan doc's table;
// -> resource table exactly. Every row here must match the service's table;
// changing an entry without a corresponding plan-doc update is a regression.
func TestClassifyOperation_RouteMatrix(t *testing.T) {
cases := []struct {
+1 -1
View File
@@ -211,7 +211,7 @@ func (reg *Registry) ListImages(ctx context.Context, account, repo string) ([]Im
// DeleteImage removes an image by tag and/or digest. A tag deletes only that tag
// pointer; a digest deletes the manifest meta and every tag pointing at it. Blob
// and manifest-object GC is deferred (siv-361). The resolved digest is returned.
// and manifest-object GC is deferred. The resolved digest is returned.
// A missing image yields ErrImageNotFound.
func (reg *Registry) DeleteImage(ctx context.Context, account, repo, tag, digest string) (string, error) {
scoped := reg.forAccount(account)
+1 -1
View File
@@ -23,7 +23,7 @@ func ecsActionFromTarget(target string) string {
// ECS_Request dispatches AWS JSON 1.1 ECS control-plane requests. The action
// comes from X-Amz-Target; errors are returned as awserrors codes and rendered
// by the shared ErrorHandler. Every action is a NotImplemented (501) stub until
// its real handler lands in a later Phase 4 sprint.
// its real handler is added when the service supports the operation.
func (gw *GatewayConfig) ECS_Request(w http.ResponseWriter, r *http.Request) error {
action := ecsActionFromTarget(r.Header.Get("X-Amz-Target"))
if action == "" {
+2 -2
View File
@@ -120,8 +120,8 @@ type GatewayConfig struct {
// routes. Nil falls back to no external providers (self-host models only).
BedrockCredentials *gateway_bedrock.CredentialStore
// BedrockEndpoints maps a self-hosted modelId to its OpenAI-compatible base
// URL. Phase 1 endpoints are pinned/always-resident, so this is static
// config; a later phase backs it with the daemon's dynamic endpoint
// URL. These endpoints are pinned/always-resident, so this is static
// config; a future implementation can back it with the daemon's dynamic endpoint
// registry. Nil/empty means no self-hosted models are reachable.
BedrockEndpoints map[string]string
// BedrockLoggingConfig persists per-account invocation-logging preferences
@@ -737,7 +737,7 @@ func TestDescribeImages_InvalidConfigJSON(t *testing.T) {
assert.Empty(t, result.Images)
}
// TestDescribeImages_ConfigFaultCounting covers mulga-7r6pm: a config.json that
// TestDescribeImages_ConfigFaultCounting covers a config.json that
// exists but can't be fetched/read/parsed must be promoted to WARN and counted
// as unreadable so it stays visible at INFO, while a genuinely absent
// config.json (a non-AMI directory) stays silent at DEBUG and uncounted, and a
@@ -1940,7 +1940,7 @@ func TestTerminateStoppedInstance_UserVolumeDeleted(t *testing.T) {
assert.Equal(t, []string{"vol-user-001"}, vd.deleted)
}
// TestTerminateStoppedInstance_StampsTeardownVolumesDone locks the mulga-1dzx9
// TestTerminateStoppedInstance_StampsTeardownVolumesDone locks the
// fix: TerminateStoppedInstance must stamp Teardown[vm.TeardownVolumes] on
// success, mirroring the running-instance path's markTeardownResult
// (vm/shutdown.go). Without this, daemon.leakedVolumeInstances() and
@@ -1492,7 +1492,7 @@ func TestDeleteVolume_EmptyStateUnattachedDeletable(t *testing.T) {
}
// TestDeleteVolumeOnTerminate_ClearsAttachmentThenDeletes locks the
// mulga-1dzx9 fix: a DeleteOnTermination root volume left attached to a
// A DeleteOnTermination root volume left attached to a
// stopped instance (Stop's Unmount deliberately never clears a Boot volume's
// AttachedInstance, daemon/vm_adapters.go) hits DeleteVolume's in-use guard
// directly — see TestDeleteVolume_VolumeAttachedButAvailable above.
+2 -2
View File
@@ -1169,7 +1169,7 @@ func TestIsEIPOwned_MultipleRecords_OneMatches(t *testing.T) {
assert.True(t, owned)
}
// --- validateSGAttachment (Phase 2.2) ---
// --- validateSGAttachment ---
func TestValidateSGAttachment_OK(t *testing.T) {
svc := setupTestVPCService(t)
@@ -1224,7 +1224,7 @@ func TestValidateSGAttachment_CrossVPC(t *testing.T) {
assert.ErrorContains(t, err, "InvalidParameterValue")
}
// Phase 7: ModifyNetworkInterfaceAttribute must propagate vpcd errors so the
// ModifyNetworkInterfaceAttribute must propagate vpcd errors so the
// caller knows OVN port-group reconciliation didn't happen. The KV record
// already changed by this point — that's intentional (reconciler converges).
func TestModifyNetworkInterfaceAttribute_VpcdError_Propagated(t *testing.T) {
@@ -1112,7 +1112,7 @@ func TestDescribeSecurityGroups_FilterNoResults(t *testing.T) {
assert.Empty(t, out.SecurityGroups)
}
// --- Phase 3: default SG lifecycle, dependency checks, quotas ---
// --- default SG lifecycle, dependency checks, quotas ---
// findDefaultSGInVPC returns the default-SG ID for a VPC by filtering on
// IsDefault=true (production code keys off IsDefault, not GroupName).
@@ -1302,7 +1302,7 @@ func TestEnsureDefaultVPC_CreatesDefaultSG(t *testing.T) {
require.Len(t, desc.SecurityGroups, 1)
}
// --- Phase 5.3: SourceSG existence + same-VPC validation on Authorize ---
// --- SourceSG existence + same-VPC validation on Authorize ---
func TestAuthorizeSecurityGroupIngress_SourceSG_SameVPC(t *testing.T) {
svc := setupTestVPCService(t)
@@ -1439,7 +1439,7 @@ func TestAuthorizeSecurityGroupEgress_RuleLimit(t *testing.T) {
assert.Contains(t, err.Error(), "RulesPerSecurityGroupLimitExceeded")
}
// --- Phase 7: vpcd error propagation across the SG surface ---
// --- vpcd error propagation across the SG surface ---
// CreateSecurityGroup writes to KV before requesting vpcd, so the record
// persists on vpcd error — the orphan-PG reconciler is the safety net. This
+1 -1
View File
@@ -4,7 +4,7 @@
// subject shape and message schema have a single source of truth.
//
// Cluster identity is "<accountID>.<clusterName>" (matching the KV layout), not a
// UUID. The Phase 5 per-AZ NATS cutover inserts "{azID}" after "ecs." — keep
// UUID. The per-AZ NATS routing inserts "{azID}" after "ecs." — keep
// these builders the one place that knows the shape so that change stays local.
package bus
+1 -1
View File
@@ -82,7 +82,7 @@ func TestContainerDefsFromAWS_SkipsNil(t *testing.T) {
assert.Empty(t, containerDefsFromAWS([]*ecs.ContainerDefinition{nil}))
}
// TestContainerDefsFromAWS_GPU covers mulga-11opz (Epic C task C1): a
// TestContainerDefsFromAWS_GPU verifies that a
// resourceRequirements entry of type=GPU is parsed as a whole-GPU count and
// carried onto ContainerDef, its AWS round trip, and the bus AssignContainer.
func TestContainerDefsFromAWS_GPU(t *testing.T) {
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
)
// TestRegisterTaskDefinition_RejectsSecrets covers siv-458: a container secrets[]
// TestRegisterTaskDefinition_RejectsSecrets verifies that a container secrets[]
// is hard-rejected rather than silently dropped.
func TestRegisterTaskDefinition_RejectsSecrets(t *testing.T) {
svc, _ := newTestService(t)
@@ -27,7 +27,7 @@ func TestRegisterTaskDefinition_RejectsSecrets(t *testing.T) {
assert.Contains(t, err.Error(), "InvalidParameterException")
}
// TestRegisterTaskDefinition_AcceptsUnsupportedLogDriver covers siv-455/458: a
// TestRegisterTaskDefinition_AcceptsUnsupportedLogDriver verifies that a
// non-json-file driver is accepted for parity (warned, not rejected) and the
// driver round-trips through DescribeTaskDefinition.
func TestRegisterTaskDefinition_AcceptsUnsupportedLogDriver(t *testing.T) {
@@ -77,8 +77,8 @@ func TestRegisterTaskDefinition_EchoesRequiresCompatibilities(t *testing.T) {
assert.Equal(t, []string{"EC2"}, aws.StringValueSlice(d.TaskDefinition.RequiresCompatibilities))
}
// TestRunTask_AssignCarriesExecutionRoleAndLogDriver covers siv-459 (execution
// role plumbed to the agent) and siv-455 (log driver reaches the assign).
// TestRunTask_AssignCarriesExecutionRoleAndLogDriver verifies that execution
// role plumbed to the agent) and the log driver reaches the assign.
func TestRunTask_AssignCarriesExecutionRoleAndLogDriver(t *testing.T) {
svc, _ := newTestService(t)
_, err := svc.CreateCluster(context.Background(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, testAccountID)
@@ -113,7 +113,7 @@ func TestRunTask_AssignCarriesExecutionRoleAndLogDriver(t *testing.T) {
assert.Equal(t, LogDriverJSONFile, poll.Assignments[0].Containers[0].LogDriver)
}
// TestRunTask_AssignCarriesGPU covers mulga-11opz (Epic C task C1): a
// TestRunTask_AssignCarriesGPU verifies that a
// resourceRequirements GPU count on the task def is threaded end-to-end —
// conv -> ContainerDef -> task-level aggregate (TaskRecord.GPU) -> the
// AssignContainer the agent polls for.
+4 -4
View File
@@ -28,7 +28,7 @@ func createInput(name string) *eks.CreateClusterInput {
// A create that fails after the NLB is provisioned must leave the NLB ARNs
// persisted on the (now FAILED) meta, otherwise the resources leak with no
// owning record and DeleteCluster cannot reclaim them (bead 165.3).
// owning record and DeleteCluster cannot reclaim them.
func TestCreateCluster_NLBArnPersistedBeforeLaterFailure(t *testing.T) {
f := newEKSServiceFixture(t)
@@ -235,7 +235,7 @@ func TestCreateCluster_FailedClusterIsReclaimedOnRetry(t *testing.T) {
assert.NotEmpty(t, f.nlb.deleteLBCalls, "reclaim must tear down the failed attempt's NLB")
}
// A FAILED cluster is observable via Describe and List (bead 165.6) — no state
// A FAILED cluster is observable via Describe and List — no state
// where create blocks but describe/list show nothing.
func TestCreateCluster_FailedClusterVisibleInDescribeAndList(t *testing.T) {
f := newEKSServiceFixture(t)
@@ -262,7 +262,7 @@ func TestCreateCluster_FailedClusterVisibleInDescribeAndList(t *testing.T) {
// An ACTIVE cluster whose reconciler recorded a /healthz failure must surface
// that as a ClusterHealth issue in describe-cluster, so a dead control plane is
// visible behind the still-ACTIVE status (bead 165.13).
// visible behind the still-ACTIVE status .
func TestDescribeCluster_SurfacesHealthIssueForActiveCluster(t *testing.T) {
f := newEKSServiceFixture(t)
@@ -295,7 +295,7 @@ func TestDescribeCluster_HealthyActiveClusterHasNoIssues(t *testing.T) {
// A missing eks-server AMI is an operator/config gap surfaced during the async
// launch: CreateCluster accepts the request (CREATING) and the background launch
// marks the half-built cluster FAILED so the reclaim path can retry (bead 165.4).
// marks the half-built cluster FAILED so the reclaim path can retry .
func TestCreateCluster_MissingAMIReturnsServiceUnavailable(t *testing.T) {
f := newEKSServiceFixture(t)
f.ami.describeOut = &ec2.DescribeImagesOutput{} // no images tagged managed-by=eks
+2 -2
View File
@@ -424,7 +424,7 @@ func TestDeleteCluster_ManagedCPVPCReclaimsCustomerVPCSGs(t *testing.T) {
}
// TestDeleteCluster_ManagedCPVPCAlreadyGoneClearsStateRecord locks the
// mulga-jbb2i teardown-idempotency fix: when the managed CP VPC is already
// Teardown is idempotent when the managed CP VPC is already
// gone (its tag-indexed EC2 record is absent — the "InvalidVpcID.NotFound"
// case confirmed live), DeleteClusterCPVPC converges to success, and the
// internal cp-vpc state record (meta.ManagedCPVPC) must be cleared right away —
@@ -491,7 +491,7 @@ func subscribeVPCDelete(t *testing.T, nc *nats.Conn) func() string {
}
// TestDeleteClusterCPVPC_OVNGCSelection locks the OVN-GC half of the
// mulga-jbb2i fix: DeleteClusterCPVPC must republish vpc.delete for the CP
// DeleteClusterCPVPC must republish vpc.delete for the CP
// VPC so vpcd's topology manager gets another chance to remove the orphaned
// logical router/subnets/egress-drop policies — both when the EC2 VPC is
// actually deleted this call, and when it was already gone, where the only
@@ -166,7 +166,7 @@ func TestDeletingReaper_ExhaustsAfterMaxAttempts(t *testing.T) {
assert.Len(t, f.inst.terminateCalls, terminateCallsAtExhaustion, "an exhausted cluster must never be re-driven again")
}
// TestDeletingReaper_PersistsLastErrorOnFailure guards mulga-djaep criterion 2:
// TestDeletingReaper_PersistsLastErrorOnFailure guards the second cleanup condition:
// a terminal give-up must carry the last error, not just a bare boolean flag.
// The error must be visible on the very first failed attempt (so an operator
// checking a still-retrying cluster already sees why), and the same error must
+1 -1
View File
@@ -47,7 +47,7 @@ func TestHTTPHandler_ProducesSpan(t *testing.T) {
// TestHTTPHandler_RejectedForwardedStillTraced is the load-bearing assertion
// for the middleware-ordering call: otelsetup.HTTPMiddleware wraps OUTSIDE
// rejectForwarded, so an X-Forwarded-For request rejected by the SSRF guard
// still produces a span (the 431+431 rooted fan-out gap the bead reports
// still produces a span (the 431+431 rooted fan-out gap
// starts on requests exactly like this) — but the rejection itself is
// unaffected by tracing being present. A span cannot let a rejected request
// through; it only observes it.
+3 -3
View File
@@ -16,7 +16,7 @@ import (
// BedrockUsageReader resolves accountID's current-period Bedrock token usage
// (input+output, across every model), the counter gateway_bedrock's usage
// consumer maintains from the invocation stream (mulga-vmfng.7.6). The
// consumer maintains from the invocation stream. The
// interface is defined here rather than importing gateway_bedrock so the two
// packages stay decoupled; gateway_bedrock.UsageStore satisfies it
// structurally, and awsgw.go wires the two together at startup.
@@ -39,7 +39,7 @@ func (s *Service) SetBedrockUsage(r BedrockUsageReader) {
// gate: the invocation that crosses the cap is itself allowed (its cost
// isn't known until it finishes), and the next one is rejected once the
// stream-fed counter catches up. A few seconds of lag behind real usage is
// an accepted property of a monthly cap (D8).
// an accepted property of a monthly cap.
func (s *Service) CheckBedrockTokens(ctx context.Context, accountID string) error {
if s == nil || !s.limits.TokensPerMonthEnabled || accountID == utils.GlobalAccountID {
return nil
@@ -192,7 +192,7 @@ func (l *rpmLimiter) snapshot() map[string]int {
// KVBucketBedrockRPMUsage is the gateway-owned KV bucket RunBedrockRPMSync
// writes each account's current bucket occupancy to, keyed by accountID. It
// exists for cross-node visibility and dashboards only: CheckBedrockRPM never
// reads it back. Enforcement must stay local and immediate (D8) — reading a
// reads it back. Enforcement must stay local and immediate — reading a
// remote value here would reintroduce exactly the round-trip latency that
// ruled out stream-fed RPM enforcement in the first place. Concurrent
// gateways overwrite each other's snapshot for the same account with no
+2 -2
View File
@@ -29,7 +29,7 @@ const KVBucketQuotaReconcile = "spinifex-quota-reconcile"
// false) is a valid no-op, so gateways without a [quota] block are unaffected.
//
// TokensPerMonthEnabled and RequestsPerMinuteEnabled are deliberately
// separate from Enabled above (mulga-vmfng.7.6): a deployment already
// separate from Enabled above: a deployment already
// enforcing standing-infra quotas (Enabled=true for VCPUs/VPCs/...) must opt
// in to Bedrock token/RPM enforcement independently, since a bare Enabled
// would otherwise silently cap Bedrock usage at zero the moment any other
@@ -60,7 +60,7 @@ type Service struct {
usage jetstream.KeyValue
// bedrockUsage resolves the stream-fed Bedrock token counter
// (mulga-vmfng.7.6). Nil until SetBedrockUsage is called, in which case
// Nil until SetBedrockUsage is called, in which case
// CheckBedrockTokens treats the dimension as unconfigured rather than
// erroring — a wiring gap must not block every Bedrock call.
bedrockUsage BedrockUsageReader
+1 -1
View File
@@ -66,7 +66,7 @@ type ParsedARN struct {
// arn:aws:rds:{region}:{accountID}:{kind}:{identifier}
const arnSegmentCount = 7
// Parses one of the four D17 ARNs and validates it belongs to the caller.
// Parses one of the four supported RDS ARNs and validates it belongs to the caller.
// Region and account are checked here rather than at policy evaluation, so a
// foreign-account reference never reaches the evaluator at all.
func ParseARN(arn, region, accountID string) (ParsedARN, error) {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require"
)
// Every D17 ARN the builders mint must parse back into the parts it was built
// Every ARN the builders mint must parse back into the parts it was built
// from, so a resource named by a describe is addressable by a tag action.
func TestParseARN_RoundTripsEveryResourceType(t *testing.T) {
cases := []struct {
+13 -13
View File
@@ -15,13 +15,13 @@ import (
"github.com/nats-io/nats.go/jetstream"
)
// Automated backups (D11 Phase A): one snapshot per instance per day inside its
// backup window, taken through rds-8's snapshot path and swept past
// Automated backups: one snapshot per instance per day inside its
// backup window, taken through the shared snapshot path and swept past
// BackupRetentionPeriod. PITR, WAL archiving and LatestRestorableTime are Phase B
// and stay unreported, so nothing here implies continuous recovery.
const (
// AWS's own bound is 35 days; this platform's is 7 (D11 Phase A). The cost of
// AWS's own bound is 35 days; this platform's is 7. The cost of
// a retained snapshot here is metadata and restore surface, not chunks: any
// snapshot latches viperblock chunk GC off for the life of the source volume,
// so one day and seven pin exactly the same data.
@@ -60,7 +60,7 @@ const (
automatedBackupRetryShiftCap = 3
)
// The operator-tunable half of rds-9: bounds and defaults, never a per-instance
// The operator-tunable backup settings: bounds and defaults, never a per-instance
// setting — those live on the DB instance record.
type BackupPolicy struct {
// The upper bound ModifyDBInstance and CreateDBInstance accept. Zero takes
@@ -131,7 +131,7 @@ func (s *Service) windowBlock(configured, fallback, kind string) dailyWindow {
}
// The window in force for this instance. A record that names one uses it; one
// that does not — a record written before rds-9, or an instance whose create
// that does not — a record written before these settings existed, or an instance whose create
// named neither — takes the deterministic assignment, which is the same value
// this phase persists at create.
func (s *Service) resolvedBackupWindow(rec *DBInstanceRecord) (dailyWindow, error) {
@@ -177,7 +177,7 @@ func (s *Service) scheduledBackupWindow(rec *DBInstanceRecord) (dailyWindow, boo
return window, err == nil
}
// The windows as a describe reports them. A record written before rds-9 carries
// The windows as a describe reports them. A record written before these settings existed carries
// neither, so the derived window is reported rather than an empty string: it is
// the window the scheduler will actually use, and reporting nothing would have the
// customer believe no backup is scheduled.
@@ -206,7 +206,7 @@ var backupCapableStatuses = []Status{StatusAvailable, StatusStopped}
// Fires this instance's automated backup when its window is open and none has
// succeeded since the window opened, reporting whether it took one. Called from
// the leader-elected reconciler pass, which is what makes it cluster-singular;
// rds-8's per-instance in-flight guard is what makes it safe anyway.
// the per-instance in-flight guard is what makes it safe anyway.
func (s *Service) runBackupWindow(ctx context.Context, kv jetstream.KeyValue, rev uint64,
accountID string, rec *DBInstanceRecord) (bool, error) {
now := time.Now().UTC()
@@ -235,9 +235,9 @@ func (s *Service) runBackupWindow(ctx context.Context, kv jetstream.KeyValue, re
}
if err := s.takeAutomatedBackup(ctx, kv, rev, accountID, rec, now); err != nil {
// D-rds-9: the database is healthy and stays available. The failure is
// The database is healthy and stays available. The failure is
// counted and evented so it is visible, and retried while the window is
// open; it never reaches rds-6's recovery ladder.
// open; it never reaches the instance recovery path.
return false, s.recordBackupFailure(ctx, kv, accountID, rec, err)
}
return true, nil
@@ -266,7 +266,7 @@ func backupRetryDelay(failures int) time.Duration {
return automatedBackupRetryBase << min(max(failures-1, 0), automatedBackupRetryShiftCap)
}
// Takes the snapshot through rds-8's path, indexes it, and stamps the window as
// Takes the snapshot through the shared path, indexes it, and stamps the window as
// fired. The index is written before the stamp: an indexed backup with no stamp
// costs one duplicate snapshot at worst and is swept on schedule, while a stamped
// backup with no index would be invisible to retention for the life of the
@@ -442,7 +442,7 @@ func (s *Service) DescribeDBInstanceAutomatedBackups(ctx context.Context,
return &rds.DescribeDBInstanceAutomatedBackupsOutput{DBInstanceAutomatedBackups: backups}, nil
}
// D19: a filter this phase cannot honour is rejected rather than dropped, since a
// A filter this phase cannot honour is rejected rather than dropped, since a
// silently unfiltered list reads as a complete answer. Returns the DB instance to
// report on, empty for every one.
func validateDescribeAutomatedBackupsRequest(input *rds.DescribeDBInstanceAutomatedBackupsInput) (string, error) {
@@ -464,11 +464,11 @@ func validateDescribeAutomatedBackupsRequest(input *rds.DescribeDBInstanceAutoma
// RestoreWindow and LatestRestorableTime are deliberately absent: this phase
// backs discrete daily snapshots, and reporting a restore window would tell a
// client it can recover to any instant inside it (D11 Phase B).
// client it can recover to any instant inside it.
func (s *Service) projectAutomatedBackup(rec *DBInstanceRecord, snapshots int) *rds.DBInstanceAutomatedBackup {
// AWS's own vocabulary: creating until the first snapshot exists, active once
// one does. retained — an automated backup outliving its instance — is not
// offered, because it would pin the source data volume indefinitely (D10).
// offered, because it would pin the source data volume indefinitely.
status := "creating"
if snapshots > 0 {
status = "active"
+4 -4
View File
@@ -15,7 +15,7 @@ import (
)
// The retention sweep is a cluster-wide vm.Reaper rather than a reconciler tick,
// because it is the one part of rds-9 that deletes customer data. The shared GC
// because it is the one part of retention that deletes customer data. The shared GC
// backstop buys it the two properties that matters most for: it is skipped
// entirely while KV is unhealthy, so it can never reap against a desired state it
// cannot read, and cluster-wide scope is leader-gated by the framework. Backup
@@ -100,7 +100,7 @@ func (r *BackupRetentionReaper) sweepAccount(ctx context.Context, kv jetstream.K
}
}
// The backstop for a crash between rds-8's last DeleteDBSnapshot and the
// The backstop for a crash between the last DeleteDBSnapshot and the
// inline volume delete that follows it: nothing else references the volume by
// then, so without this it is orphaned permanently.
volumes, err := r.svc.reclaimOrphanedVolumes(ctx, kv)
@@ -289,7 +289,7 @@ func (s *Service) dropAutomatedBackupIndex(ctx context.Context, kv jetstream.Key
// Two callers, both of which mean "there is nothing left to retain for": a
// BackupRetentionPeriod=0 modify, which turns the feature off, and the teardown
// of the instance itself, whose automated backups would otherwise pin its data
// volume after the instance is gone (D10).
// volume after the instance is gone.
func (s *Service) purgeAutomatedBackups(ctx context.Context, kv jetstream.KeyValue,
accountID, dbInstanceIdentifier string) error {
stamps, err := ListAutomatedBackupStamps(ctx, kv, dbInstanceIdentifier)
@@ -309,7 +309,7 @@ func (s *Service) purgeAutomatedBackups(ctx context.Context, kv jetstream.KeyVal
return errors.Join(failures...)
}
// Deletes the data volumes retained for snapshots that no longer exist. rds-8
// Deletes the data volumes retained for snapshots that no longer exist. The service
// deletes the volume inline on the last DeleteDBSnapshot, so this only ever fires
// for a crash between those two steps — which is precisely what a KV-health-gated
// cluster-wide sweep is for.
+2 -2
View File
@@ -304,7 +304,7 @@ func TestSweep_FailsWhenTheAccountBucketsCannotBeEnumerated(t *testing.T) {
require.Error(t, err)
}
// The backstop for a crash between rds-8's last DeleteDBSnapshot and the inline
// The backstop for a crash between the last DeleteDBSnapshot and the inline
// volume delete that follows it: nothing else references the volume by then.
func TestSweep_ReclaimsAnOrphanedRetainedVolume(t *testing.T) {
h := newSnapshotHarness(t, false)
@@ -392,7 +392,7 @@ func TestModifyDBInstance_SweepsTheAutomatedBackupsWhenRetentionGoesToZero(t *te
assert.Empty(t, h.automatedStamps(t, testDBID))
}
// D10: an automated backup outliving its instance would pin the instance's data
// An automated backup outliving its instance would pin the instance's data
// volume for good, so teardown sweeps the set before releasing the volume.
func TestDeleteDBInstance_SweepsTheAutomatedBackups(t *testing.T) {
h := newLifecycleHarness(t, false)
+6 -6
View File
@@ -409,7 +409,7 @@ func TestRunBackupWindow_TakesIndexesAndStampsTheBackup(t *testing.T) {
require.True(t, h.runBackupPass(t))
// One EC2 snapshot, taken through rds-8's path: the engine was quiesced and let
// One EC2 snapshot, taken through the automated backup path: the engine was quiesced and let
// go again rather than copied mid-write.
require.Len(t, h.snaps.created, 1)
issued := make([]string, 0, 2)
@@ -506,7 +506,7 @@ func TestRunBackupWindow_SkipsAnInstanceItCannotBackUp(t *testing.T) {
assert.Len(t, h.events(t, EventSourceTypeDBInstance, testDBID), len(messages))
}
// D-rds-9: a failed backup is a backup fault, never an instance fault. The
// A failed backup is a backup fault, never an instance fault. The
// database stays available and the failure is counted so it is visible.
func TestRunBackupWindow_CountsAFailureAndLeavesTheInstanceAvailable(t *testing.T) {
h := newSnapshotHarness(t, false)
@@ -542,7 +542,7 @@ func TestRunBackupWindow_IgnoresAMalformedStoredWindow(t *testing.T) {
// The other half of the pair is resolved for the overlap check, not to be
// written down. A record that carried no maintenance window before the modify
// still carries none after it: reporting one back would show as drift in the next
// plan of a config that never set it.
// configuration read for a request that never set it.
func TestModifyDBInstance_DoesNotPersistTheWindowItWasNotGiven(t *testing.T) {
h := newSnapshotHarness(t, false)
rec := retainingRecord(7)
@@ -561,7 +561,7 @@ func TestModifyDBInstance_DoesNotPersistTheWindowItWasNotGiven(t *testing.T) {
assert.Empty(t, stored.PreferredMaintenanceWindow, "a window the request did not name is not stored")
}
// A record written before rds-9 carries no window at all, and still has to be
// A record written before window fields existed carries no window at all, and still has to be
// backed up — on the window a describe reports for it.
func TestResolvedBackupWindow_DerivesAWindowForARecordWithoutOne(t *testing.T) {
svc := NewService(nil, testRegion)
@@ -710,7 +710,7 @@ func TestDescribeDBInstanceAutomatedBackups_ReportsTheBackupSet(t *testing.T) {
assert.Equal(t, int64(7), aws.Int64Value(backup.BackupRetentionPeriod))
assert.Equal(t, testRegion, aws.StringValue(backup.Region))
// D11 Phase B: this phase backs discrete daily snapshots. A restore window
// This phase backs discrete daily snapshots. A restore window
// would tell a client it can recover to any instant inside it.
assert.Nil(t, backup.RestoreWindow)
}
@@ -736,7 +736,7 @@ func TestDescribeDBInstanceAutomatedBackups_RejectsAnUnknownInstance(t *testing.
assert.Contains(t, err.Error(), awserrors.ErrorDBInstanceNotFound)
}
// D19: a filter this phase cannot honour is rejected, because a silently
// A filter this phase cannot honour is rejected, because a silently
// unfiltered list reads as a complete answer.
func TestDescribeDBInstanceAutomatedBackups_RejectsUnimplementedFilters(t *testing.T) {
cases := map[string]*rds.DescribeDBInstanceAutomatedBackupsInput{
+8 -8
View File
@@ -14,7 +14,7 @@ import (
"github.com/nats-io/nats.go"
)
// The control-plane half of the rds-2b command channel. The agent side is a
// The control-plane half of the command channel. The agent side is a
// long poll over the gateway; this publishes on the bus subject that poll is
// subscribed to and waits for the reply the agent carries back on its next one.
@@ -23,10 +23,10 @@ const (
CommandApplyParams = "apply-params"
CommandStopEngine = "stop-engine"
// Extends the in-guest filesystem onto a data volume the control plane has
// already grown (D12).
// already grown.
CommandGrowFilesystem = "grow-filesystem"
// Holds the engine at a checkpoint for the length of a snapshot, and releases
// it again (D10). The hold is a session the agent keeps open, so the pair is
// it again. The hold is a session the agent keeps open, so the pair is
// two commands rather than one with a duration.
CommandQuiesce = "quiesce"
CommandUnquiesce = "unquiesce"
@@ -67,7 +67,7 @@ const (
// time in backup mode rather than the rest of its life.
quiesceHold = 6 * time.Minute
// The channel is a live subscription rather than a durable queue (D8), so a
// The channel is a live subscription rather than a durable queue, so a
// command published between two of the agent's polls reaches nobody.
// Re-publishing on this interval closes that gap without queueing anything.
commandRepublishEvery = 2 * time.Second
@@ -80,7 +80,7 @@ var ErrCommandUnreachable = errors.New("rds: the instance agent did not answer t
// Sets the engine's master password live. It is never persisted anywhere: an
// unreachable agent fails loudly rather than leaving cleartext queued for
// later (D8).
// later.
func (s *Service) setMasterPassword(ctx context.Context, accountID, dbInstanceIdentifier, username, password string) error {
_, err := s.issueCommand(ctx, accountID, dbInstanceIdentifier, CommandSetPassword, setPasswordTimeout, []Parameter{
{Name: CommandParamMasterUsername, Value: username},
@@ -91,7 +91,7 @@ func (s *Service) setMasterPassword(ctx context.Context, accountID, dbInstanceId
// Writes the resolved parameter set into the engine's config and reloads it.
// Returns the settings the engine accepted but will not apply until it
// restarts, which is what RebootDBInstance then clears (D16).
// restarts, which is what RebootDBInstance then clears.
func (s *Service) applyParameters(ctx context.Context, accountID, dbInstanceIdentifier string, params []Parameter) ([]string, error) {
reply, err := s.issueCommand(ctx, accountID, dbInstanceIdentifier, CommandApplyParams, applyParamsTimeout, params)
if err != nil {
@@ -109,7 +109,7 @@ func (s *Service) stopEngine(ctx context.Context, accountID, dbInstanceIdentifie
// Extends the guest's filesystem onto the already-grown data volume. Issued
// once the agent is back rather than during boot: both ext4 and XFS grow while
// mounted, so this needs no ordering against the engine start (D12).
// mounted, so this needs no ordering against the engine start.
func (s *Service) growFilesystem(ctx context.Context, accountID, dbInstanceIdentifier string) error {
_, err := s.issueCommand(ctx, accountID, dbInstanceIdentifier, CommandGrowFilesystem, growFilesystemTimeout, nil)
return err
@@ -118,7 +118,7 @@ func (s *Service) growFilesystem(ctx context.Context, accountID, dbInstanceIdent
// Holds the engine at a checkpoint so the data volume can be snapshotted at a
// consistent point. The agent keeps the backup session open until the release
// below, or until the deadline expires — so a control plane that dies here does
// not leave the engine in backup mode indefinitely (D10).
// not leave the engine in backup mode indefinitely.
func (s *Service) quiesceEngine(ctx context.Context, accountID, dbInstanceIdentifier, label string) error {
_, err := s.issueCommand(ctx, accountID, dbInstanceIdentifier, CommandQuiesce, quiesceTimeout, []Parameter{
{Name: CommandParamQuiesceLabel, Value: label},
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
)
// D8: the password is handed straight to the agent and never persisted, so the
// The password is handed straight to the agent and never persisted, so the
// command is the only place it exists between the request and the engine.
func TestSetMasterPassword_CarriesTheCredentialsToTheAgent(t *testing.T) {
h := newLifecycleHarness(t, false)
@@ -34,7 +34,7 @@ func TestSetMasterPassword_FailsWhenTheAgentRejectsIt(t *testing.T) {
assert.Contains(t, err.Error(), CommandSetPassword)
}
// D16: the settings the engine accepted but will not honour until it restarts
// The settings the engine accepted but will not honour until it restarts
// come back as the reply message, which is what a later reboot then clears.
func TestApplyParameters_ReportsWhatIsPendingARestart(t *testing.T) {
h := newLifecycleHarness(t, false)
+4 -4
View File
@@ -14,7 +14,7 @@ import (
"github.com/nats-io/nats.go/jetstream"
)
// The first VM behind a DB instance. rds-5b and rds-6 increment it on replace,
// The first VM behind a DB instance. Replacement and recovery increment it,
// so an agent report carrying an older generation is a superseded VM.
const firstVMGeneration = 1
@@ -22,7 +22,7 @@ const firstVMGeneration = 1
// the identifier, place and launch the dual-NIC VM with its data volume and
// customer ENI, seed the one-shot bootstrap config, and publish the endpoint
// record. The instance is returned at status=creating; the reconciler flips it
// to available on the first healthy agent heartbeat (D7).
// to available on the first healthy agent heartbeat.
func (s *Service) CreateDBInstance(ctx context.Context, input *rds.CreateDBInstanceInput, accountID string) (out *rds.CreateDBInstanceOutput, err error) {
req, err := s.validateCreateRequest(input)
if err != nil {
@@ -39,7 +39,7 @@ func (s *Service) CreateDBInstance(ctx context.Context, input *rds.CreateDBInsta
// Resolved before the identifier is reserved, so a create naming a group that
// does not exist leaves no record behind. The set is literals only: the
// class's memory has already been folded into every size-derived default, so
// the agent never sees a formula (D20).
// the agent never sees a formula.
parameters, err := s.resolveGroupParameters(ctx, kv, accountID, req.DBParameterGroupName, req.InstanceClass)
if err != nil {
return nil, err
@@ -202,7 +202,7 @@ func (s *Service) recordLaunch(ctx context.Context, kv jetstream.KeyValue, key,
}
rec.DNSName = s.dnsName(accountID, rec.DBInstanceIdentifier)
// Without northstar there is no resolvable name, so the endpoint is the ENI
// IP itself — stable per D5 and therefore as durable as a hostname (D6).
// IP itself — stable across VM replacement and therefore as durable as a hostname.
rec.EndpointAddress = rec.DNSName
if rec.EndpointAddress == "" {
rec.EndpointAddress = rec.ENIPrivateIP
+3 -3
View File
@@ -372,7 +372,7 @@ func TestCreateDBInstance_FailureAfterLaunchUnwindsEveryResource(t *testing.T) {
assert.Len(t, h.launch.enis.deleted, 2, "both the customer and system ENI are deleted")
}
// D19 honesty: the request may not be answered with an instance whose storage is
// The request may not be answered with an instance whose storage is
// not actually encrypted, so an unencrypted volume fails the whole create.
func TestCreateDBInstance_UnencryptedDataVolumeFailsTheCreate(t *testing.T) {
h := newCreateHarness(t, "")
@@ -426,7 +426,7 @@ func TestCreateDBInstance_RequiresADefaultVPCWithASubnet(t *testing.T) {
// need one. A zero policy carries the built-in defaults.
func newCreateValidator() *Service { return NewService(nil, testRegion) }
// D19: a parameter that would create a false safety, security or availability
// A parameter that would create a false safety, security or availability
// guarantee is rejected rather than silently dropped.
func TestValidateCreateRequest_RejectsUnimplementedParameters(t *testing.T) {
cases := []struct {
@@ -460,7 +460,7 @@ func TestValidateCreateRequest_RejectsUnimplementedParameters(t *testing.T) {
}
}
// DeletionProtection is honoured from rds-5a onwards, so it has to be accepted
// DeletionProtection is honoured, so it has to be accepted
// at create and carried onto the record DeleteDBInstance checks.
func TestValidateCreateRequest_AcceptsDeletionProtection(t *testing.T) {
input := validCreateInput()
+4 -4
View File
@@ -193,7 +193,7 @@ func (s *Service) teardownDBInstance(ctx context.Context, kv jetstream.KeyValue,
// Before the volume is released, so the automated backups are no longer holding
// its chunks when that decides between deleting and retaining it. AWS keeps
// automated backups after a delete as a separate resource; doing that here
// would pin the data volume indefinitely under rds-8's retention rule (D10).
// would pin the data volume indefinitely under the retention rule.
if err := s.purgeAutomatedBackups(ctx, kv, accountID, rec.DBInstanceIdentifier); err != nil {
return fmt.Errorf("rds: sweep the automated backups of %s: %w", rec.DBInstanceIdentifier, err)
}
@@ -248,7 +248,7 @@ func (s *Service) reserveFinalSnapshot(ctx context.Context, kv jetstream.KeyValu
}
// Manual, deliberately: the customer named it, so only the customer removes it
// — which is also what keeps rds-9's retention sweep away from it.
// — which is also what keeps the retention sweep away from it.
record := newDBSnapshotRecord(accountID, rec, &validatedSnapshot{
DBSnapshotIdentifier: identifier,
SnapshotType: SnapshotTypeManual,
@@ -443,7 +443,7 @@ func (s *Service) recordFinalSnapshotCreated(ctx context.Context, accountID stri
// A viperblock snapshot references its source volume's chunk files rather than
// copying them, so the volume cannot be deleted while any snapshot survives —
// including the final one just taken. It is retained instead, recorded with the
// snapshots holding it so the last DeleteDBSnapshot can release it (D10).
// snapshots holding it so the last DeleteDBSnapshot can release it.
func (s *Service) releaseDataVolume(ctx context.Context, kv jetstream.KeyValue, accountID string, rec *DBInstanceRecord) error {
if rec.DataVolumeID == "" {
return nil
@@ -476,7 +476,7 @@ func (s *Service) releaseDataVolume(ctx context.Context, kv jetstream.KeyValue,
}
// Records a volume the teardown left behind, with the snapshots holding it so
// the last DeleteDBSnapshot can release it (D10). holdersUnresolved marks the
// the last DeleteDBSnapshot can release it. holdersUnresolved marks the
// case where the volume store refused the delete but named nobody, so a release
// has to re-check rather than trust an empty list.
func (s *Service) retainDataVolume(ctx context.Context, kv jetstream.KeyValue, accountID string,
+2 -2
View File
@@ -120,7 +120,7 @@ func TestDeleteDBInstance_HonoursDeletionProtection(t *testing.T) {
assert.Empty(t, h.volumes.deleted)
}
// D18: the snapshot is taken once the VM is gone, so it reads a sealed data
// The snapshot is taken once the VM is gone, so it reads a sealed data
// volume rather than one a live engine is still writing to.
func TestDeleteDBInstance_TakesTheFinalSnapshotAfterTheEngineAndVMAreDown(t *testing.T) {
h := newLifecycleHarness(t, false)
@@ -207,7 +207,7 @@ func TestDeleteDBInstance_AdoptsAnInterruptedFinalSnapshot(t *testing.T) {
assert.Equal(t, "snap-cut-before-crash", stored.SnapshotID)
}
// D10: a snapshot references its source volume's chunks, so the volume cannot
// A snapshot references its source volume's chunks, so the volume cannot
// be deleted while the final snapshot survives. It is retained and recorded
// with the snapshots holding it.
func TestDeleteDBInstance_RetainsTheVolumeAFinalSnapshotStillHolds(t *testing.T) {
+1 -1
View File
@@ -240,7 +240,7 @@ func projectPendingModifiedValues(pending *PendingModifiedValues) *rds.PendingMo
// AWS reports a parameter group's state on the membership rather than in
// PendingModifiedValues, and the Terraform provider reads it there: applying
// while a modify is draining, pending-reboot while static settings await the
// restart that adopts them (D16).
// restart that adopts them.
func projectParameterGroup(rec *DBInstanceRecord) []*rds.DBParameterGroupStatus {
if rec.DBParameterGroupName == "" {
return nil
+1 -1
View File
@@ -316,7 +316,7 @@ func TestProjectDBInstance_ReportsTheParameterGroupApplyStatus(t *testing.T) {
assert.Equal(t, "in-sync", statusOf(rec))
// D16: static settings are in the engine's config but not in effect until
// Static settings are in the engine's config but not in effect until
// the restart that adopts them.
rec.PendingRebootParameters = []string{"shared_buffers"}
assert.Equal(t, "pending-reboot", statusOf(rec))
+1 -1
View File
@@ -9,7 +9,7 @@ import (
)
// The vanity hostname for a DB instance, or "" on a deployment with no base
// domain — where the endpoint is the bare ENI IP instead (D6).
// domain — where the endpoint is the bare ENI IP instead.
func (s *Service) dnsName(accountID, dbInstanceIdentifier string) string {
if s.baseDomain == "" {
return ""
+2 -2
View File
@@ -64,7 +64,7 @@ func SupportedEngines() []string {
// An empty version takes the pin. A supplied one must name the pinned major,
// with or without a minor, since a mismatch would silently run a version the
// customer did not ask for (D19).
// customer did not ask for.
func (e Engine) ValidateVersion(version string) error {
v := strings.TrimSpace(version)
if v == "" || v == e.MajorVersion {
@@ -118,7 +118,7 @@ func (e Engine) ValidateUsernameNotReserved(username string) error {
}
// Bounds only. The password is never inspected beyond this and never stored in
// cleartext past the first bootstrap fetch (D8).
// cleartext past the first bootstrap fetch.
func ValidateMasterUserPassword(password string) error {
switch {
case password == "":
+1 -1
View File
@@ -25,7 +25,7 @@ func TestLookupEngine(t *testing.T) {
assert.Equal(t, []string{"postgres"}, SupportedEngines())
}
// D19: a version other than the pinned one would be served by an image that is
// A version other than the pinned one would be served by an image that is
// not the one asked for, so it is rejected rather than quietly substituted.
func TestEngineValidateVersion(t *testing.T) {
engine, err := LookupEngine("postgres")
+1 -1
View File
@@ -113,7 +113,7 @@ type LaunchInput struct {
RequireEncryptedData bool
// A replace re-uses the DB instance's persisted customer ENI and data
// volume instead of minting new ones (D5/D9): the endpoint address and the
// volume instead of minting new ones: the endpoint address and the
// datadir are the identity that must survive the VM. Both empty is a
// create, which builds them.
ExistingCustomerENI string
+4 -4
View File
@@ -45,8 +45,8 @@ const instanceCommandTimeout = 90 * time.Second
// How often the fleet is re-read while a stop is settling.
const vmStopPollInterval = 500 * time.Millisecond
// Reboots the engine, applying any static parameters stored pending-reboot
// (D16). ForceFailover is rejected outright: there is no standby to fail over
// Reboots the engine, applying any static parameters stored pending-reboot.
// ForceFailover is rejected outright: there is no standby to fail over
// to, and silently ignoring it would report a failover that never happened.
func (s *Service) RebootDBInstance(ctx context.Context, input *rds.RebootDBInstanceInput, accountID string) (*rds.RebootDBInstanceOutput, error) {
if input == nil {
@@ -93,12 +93,12 @@ func (s *Service) RebootDBInstance(ctx context.Context, input *rds.RebootDBInsta
// Stops the engine and then the VM. The data volume, the customer ENI and its
// IP, and the DNS record are all retained, so a start comes back on the same
// datadir at the same address (D5/D9).
// datadir at the same address.
func (s *Service) StopDBInstance(ctx context.Context, input *rds.StopDBInstanceInput, accountID string) (*rds.StopDBInstanceOutput, error) {
if input == nil {
return nil, awserrors.Errorf(awserrors.ErrorInvalidParameterValue, "empty request")
}
// A pre-stop snapshot is rds-8's; accepting it here would report a snapshot
// A pre-stop snapshot is already in progress; accepting it here would report a snapshot
// the customer would then not find.
if aws.StringValue(input.DBSnapshotIdentifier) != "" {
return nil, unimplemented("DBSnapshotIdentifier", "taking a snapshot as part of a stop is not implemented yet")
+3 -3
View File
@@ -327,7 +327,7 @@ func TestRebootDBInstance_StopsTheEngineThenTheVM(t *testing.T) {
assert.Equal(t, StatusRebooting, h.record(t).Status)
}
// D16: the static parameters are already in the engine's config, so the restart
// The static parameters are already in the engine's config, so the restart
// is what applies them and the record stops advertising them.
func TestRebootDBInstance_ClearsThePendingRebootParameters(t *testing.T) {
h := newLifecycleHarness(t, false)
@@ -343,7 +343,7 @@ func TestRebootDBInstance_ClearsThePendingRebootParameters(t *testing.T) {
assert.Contains(t, eventMessages(h.events(t)), "Applied the parameters that were pending a reboot.")
}
// D19: there is no standby to fail over to, so silently ignoring the flag would
// There is no standby to fail over to, so silently ignoring the flag would
// report a failover that never happened.
func TestRebootDBInstance_RejectsForceFailover(t *testing.T) {
h := newLifecycleHarness(t, false)
@@ -466,7 +466,7 @@ func TestReconciler_DoesNotCallAStillRunningVMStopped(t *testing.T) {
assert.Equal(t, StatusStopping, h.record(t).Status)
}
// D5/D9: the data volume, the customer ENI and the DNS record are all retained,
// The data volume, the customer ENI and the DNS record are all retained,
// so a start comes back on the same datadir at the same address.
func TestStopDBInstance_RetainsTheEndpointAndTheVolume(t *testing.T) {
h := newLifecycleHarness(t, false)
+6 -6
View File
@@ -23,7 +23,7 @@ import (
type modifyPlan struct {
// Applied on the spot whatever ApplyImmediately says, because none of them
// interrupts service and AWS applies them as soon as possible too. The
// password especially: D8 forbids persisting the cleartext, so there is
// password especially: cleartext is never persisted, so there is
// nothing that could be deferred.
MasterUserPassword string
SecurityGroupIDs []string
@@ -65,7 +65,7 @@ func (p *modifyPlan) empty() bool {
// Changes a live DB instance. The non-disruptive settings land immediately; a
// storage grow, a class change or a parameter-group change is applied now when
// ApplyImmediately is set and otherwise recorded in PendingModifiedValues for
// the maintenance window (rds-9) to drain through applyPendingModifications.
// the maintenance window to drain through applyPendingModifications.
//
// The endpoint survives every path: the data volume, the customer ENI and its
// address, and the DNS A-record are untouched by a grow and by a class change.
@@ -221,7 +221,7 @@ func (s *Service) planModify(ctx context.Context, input *rds.ModifyDBInstanceInp
if input.DeletionProtection != nil && aws.BoolValue(input.DeletionProtection) != rec.DeletionProtection {
plan.DeletionProtection = input.DeletionProtection
}
// Nothing acts on it (D19), but a value the record does not adopt is one the
// Nothing acts on it, but a value the record does not adopt is one the
// next describe contradicts, and the client re-sends it forever.
if input.AutoMinorVersionUpgrade != nil && aws.BoolValue(input.AutoMinorVersionUpgrade) != rec.AutoMinorVersionUpgrade {
plan.AutoMinorVersionUpgrade = input.AutoMinorVersionUpgrade
@@ -255,7 +255,7 @@ func (s *Service) planSecurityGroups(ctx context.Context, accountID string, rec
return requested, nil
}
// The rds-9 fields, now validated and effective rather than record-only. Both
// The backup fields, now validated and effective rather than record-only. Both
// windows are resolved as a pair even when the request names one, because AWS's
// non-overlap rule is about the pair: a request moving the backup window onto the
// stored maintenance window has to be rejected too.
@@ -311,7 +311,7 @@ func (s *Service) applyImmediateModify(ctx context.Context, kv jetstream.KeyValu
return nil
}
// Never persisted anywhere: an unreachable agent fails the call rather than
// leaving cleartext queued for a later window (D8).
// leaving cleartext queued for a later window.
if plan.MasterUserPassword != "" {
if err := s.setMasterPassword(ctx, accountID, rec.DBInstanceIdentifier, rec.MasterUsername, plan.MasterUserPassword); err != nil {
return awserrors.Errorf(awserrors.ErrorDBInstanceInvalidState,
@@ -404,7 +404,7 @@ func (s *Service) reassociateSecurityGroups(ctx context.Context, accountID strin
return nil
}
// D19: a supported action carrying an unimplemented parameter must not silently
// A supported action carrying an unimplemented parameter must not silently
// drop it. Every rejection below is a parameter whose omission would create a
// false safety, security or availability guarantee; the inert ones —
// AutoMinorVersionUpgrade, CopyTagsToSnapshot, Performance Insights, Enhanced
+8 -8
View File
@@ -145,7 +145,7 @@ func TestModifyDBInstance_AppliesTheNonDisruptiveSettingsAtOnce(t *testing.T) {
assert.Nil(t, rec.PendingModifiedValues)
}
// D8: only the fact of the rotation is recorded. A password that reached KV
// Only the fact of the rotation is recorded. A password that reached KV
// would be readable by anything that can read the bucket, forever.
func TestModifyDBInstance_NeverPersistsTheRotatedPassword(t *testing.T) {
h := newModifyHarness(t)
@@ -231,7 +231,7 @@ func TestModifyDBInstance_IgnoresTheSecurityGroupsAlreadyAttached(t *testing.T)
assert.Empty(t, h.launch.enis.modified)
}
// D12: storage is grow-only, and a request to shrink must be refused before the
// Storage is grow-only, and a request to shrink must be refused before the
// instance moves anywhere — a rejected request cannot cost a running database
// its availability.
func TestModifyDBInstance_RejectsAStorageShrinkBeforeAnythingMoves(t *testing.T) {
@@ -257,7 +257,7 @@ func TestModifyDBInstance_RejectsAStorageShrinkBeforeAnythingMoves(t *testing.T)
// Terraform sends the whole body on every apply, so a size that repeats the
// current one accompanies unrelated changes constantly. Failing it would fail
// every one of them; it contributes nothing to the plan instead.
// every one of them; it contributes nothing to the resulting configuration instead.
func TestModifyDBInstance_TreatsTheCurrentStorageSizeAsNoChange(t *testing.T) {
h := newModifyHarness(t)
seedInstance(t, h.svc, modifiableRecord())
@@ -296,7 +296,7 @@ func TestModifyDBInstance_IsANoOpWhenNothingDiffers(t *testing.T) {
assert.Nil(t, h.record(t).PendingModifiedValues)
}
// D15: the db.* classes are a facade over the platform's instance types, so a
// The db.* classes are a facade over the platform's instance types, so a
// class with nothing behind it is rejected with the set that has.
func TestModifyDBInstance_RejectsAnUnmappedInstanceClass(t *testing.T) {
h := newModifyHarness(t)
@@ -312,7 +312,7 @@ func TestModifyDBInstance_RejectsAnUnmappedInstanceClass(t *testing.T) {
assert.Equal(t, StatusAvailable, h.record(t).Status)
}
// Until rds-7 materialises real groups the implicit default is the only name
// Until real groups are materialised the implicit default is the only name
// that resolves, so any other one names a group that does not exist.
func TestModifyDBInstance_RejectsAnUnknownParameterGroup(t *testing.T) {
h := newModifyHarness(t)
@@ -327,7 +327,7 @@ func TestModifyDBInstance_RejectsAnUnknownParameterGroup(t *testing.T) {
assert.Nil(t, h.record(t).PendingModifiedValues)
}
// The rds-9 fields are stored rather than acted on, but a value outside AWS's
// These fields are stored rather than acted on, but a value outside AWS's
// range would fail in a maintenance window nobody is watching.
func TestModifyDBInstance_RejectsAnOutOfRangeBackupRetention(t *testing.T) {
h := newModifyHarness(t)
@@ -342,7 +342,7 @@ func TestModifyDBInstance_RejectsAnOutOfRangeBackupRetention(t *testing.T) {
assert.Zero(t, h.record(t).BackupRetentionPeriod)
}
// D19: a supported action carrying a parameter this platform does not implement
// A supported action carrying a parameter this platform does not implement
// is rejected, never served with the parameter quietly dropped. Each of these
// would otherwise leave the customer believing in a guarantee they do not have.
func TestModifyDBInstance_RejectsTheUnimplementedParameters(t *testing.T) {
@@ -397,7 +397,7 @@ func TestModifyDBInstance_AcceptsTheStorageTypeItAlreadyHas(t *testing.T) {
assert.True(t, h.record(t).DeletionProtection)
}
// A disruptive change without ApplyImmediately is recorded and left for rds-9's
// A disruptive change without ApplyImmediately is recorded and left for the
// maintenance window: the database keeps serving until then.
func TestModifyDBInstance_DefersADisruptiveChangeToTheMaintenanceWindow(t *testing.T) {
h := newModifyHarness(t)
+3 -3
View File
@@ -19,7 +19,7 @@ import (
// is a boot loop, and the bad config is on the persistent data volume.
// When a parameter takes effect. Static settings are stored and reported
// pending-reboot; dynamic ones are adopted by a reload (D16).
// pending-reboot; dynamic ones are adopted by a reload.
const (
ApplyTypeStatic = "static"
ApplyTypeDynamic = "dynamic"
@@ -50,7 +50,7 @@ const (
// One catalog entry. Exactly one of Default and DefaultFor is set: a literal for
// the parameters whose engine default is size-independent, and a formula over
// the instance class's memory for the ones that are not (D20).
// the instance class's memory for the ones that are not.
type ParameterSpec struct {
Name string
DataType string
@@ -441,7 +441,7 @@ func CatalogParameterNames() []string {
}
// The engine default for one parameter at one instance class: the literal, or
// the formula evaluated against the class's memory (D20).
// the formula evaluated against the class's memory.
func (s ParameterSpec) DefaultAt(memoryMiB int64) string {
if s.DefaultFor != nil {
return s.DefaultFor(memoryMiB)
+2 -2
View File
@@ -53,8 +53,8 @@ func TestParameterCatalog_EntriesAreWellFormed(t *testing.T) {
}
}
// D20: the size-derived defaults have to actually move with the class, or the
// formulas are decoration and one end of the D15 range is mis-tuned.
// The size-derived defaults have to actually move with the class, or the
// formulas are decoration and one end of the supported range is mis-tuned.
func TestParameterCatalog_SizeDerivedDefaultsScaleWithClassMemory(t *testing.T) {
smallest, err := classMemoryMiB("db.t3.micro")
require.NoError(t, err)
+3 -3
View File
@@ -129,7 +129,7 @@ func (s *Service) DescribeDBParameterGroups(ctx context.Context, input *rds.Desc
}
// Stores validated overrides, one KV key per parameter, so a modify touching one
// setting cannot clobber a concurrent change to another (D3). The whole request
// setting cannot clobber a concurrent change to another. The whole request
// is validated before anything is written: a batch with one bad value must leave
// the group exactly as it was rather than half-applied.
func (s *Service) ModifyDBParameterGroup(ctx context.Context, input *rds.ModifyDBParameterGroupInput, accountID string) (*rds.DBParameterGroupNameMessage, error) {
@@ -186,7 +186,7 @@ func (s *Service) ModifyDBParameterGroup(ctx context.Context, input *rds.ModifyD
// Merges catalog defaults with the group's stored overrides. The defaults are
// evaluated at the smallest supported class, because a parameter group is not
// bound to an instance: a customer reading a group's values before creating
// anything has to see literals, and D20 keeps formulas off this surface.
// anything has to see literals, and formulas stay off this surface.
func (s *Service) DescribeDBParameters(ctx context.Context, input *rds.DescribeDBParametersInput, accountID string) (*rds.DescribeDBParametersOutput, error) {
if input == nil {
return nil, awserrors.Errorf(awserrors.ErrorInvalidParameterValue, "empty request")
@@ -428,7 +428,7 @@ func parameterSource(isOverride bool) string {
}
// A computed default is reported as engine-default with its literal value, never
// as the formula that produced it (D20).
// as the formula that produced it.
func projectParameter(spec ParameterSpec, value string, isOverride bool) *rds.Parameter {
out := &rds.Parameter{
ParameterName: aws.String(spec.Name),
+1 -1
View File
@@ -269,7 +269,7 @@ func TestModifyDBParameterGroup_RejectsAnUnknownGroup(t *testing.T) {
"the code has to survive resolution or the client sees a 500")
}
// D20: a computed default reaches the customer as the literal it evaluated to,
// A computed default reaches the customer as the literal it evaluated to,
// never as the formula that produced it.
func TestDescribeDBParameters_ReportsComputedDefaultsAsLiterals(t *testing.T) {
h := newCreateHarness(t, testBaseDomain)
+3 -3
View File
@@ -16,7 +16,7 @@ import (
// modify that quietly does something different from the one the customer
// watched happen.
//
// rds-9 owns the trigger: its window machinery — parsing, deterministic
// The backup scheduler owns the trigger: its window machinery — parsing, deterministic
// assignment, a persisted last-fired stamp, exactly-once firing across leader
// churn — is the same mechanism a maintenance window needs, so it is built once
// there and calls this.
@@ -35,7 +35,7 @@ func (s *Service) applyPendingModifications(ctx context.Context, kv jetstream.Ke
// puts any statically-scoped setting into effect.
//
// A class change re-derives every size-derived default, so the set is
// recomputed when either the group or the class moves (D20). It is resolved
// recomputed when either the group or the class moves. It is resolved
// against the class the instance is becoming: the include lives on the data
// volume, so it is the replacement VM that adopts it.
if pending.DBParameterGroupName != "" || pending.DBInstanceClass != "" {
@@ -124,7 +124,7 @@ func (s *Service) applyPendingModifications(ctx context.Context, kv jetstream.Ke
// Re-resolves the effective set and installs it into the engine's config,
// recording the settings the engine accepted but will not honour until it
// restarts (D16). The set lives on the data volume, so it survives the VM
// restarts. The set lives on the data volume, so it survives the VM
// replace a class change performs and needs no second apply afterwards.
//
// A re-resolve, never a merge: the whole set is recomputed from the catalog and
+5 -5
View File
@@ -44,7 +44,7 @@ func TestApplyPendingModifications_GrowsStorageOnTheSameVM(t *testing.T) {
assert.Nil(t, stored.PendingModifiedValues.AllocatedStorage)
}
// D15: a class change is a VM replace, and a grow asked for alongside it rides
// A class change is a VM replace, and a grow asked for alongside it rides
// the same outage rather than opening a second one.
func TestApplyPendingModifications_ClassChangeReplacesTheVMAndCarriesTheGrow(t *testing.T) {
h := newModifyHarness(t)
@@ -71,7 +71,7 @@ func TestApplyPendingModifications_ClassChangeReplacesTheVMAndCarriesTheGrow(t *
assert.True(t, stored.PendingModifiedValues.FilesystemGrowPending)
}
// D16: the parameters go in while the engine this modify started against is
// The parameters go in while the engine this modify started against is
// still the one running, so the restart that follows is what adopts the
// statically-scoped ones.
func TestApplyPendingModifications_AppliesTheParametersBeforeTheOutage(t *testing.T) {
@@ -119,10 +119,10 @@ func TestApplyPendingModifications_KeepsThePendingRebootParametersWithoutAnOutag
assert.Equal(t, []string{"shared_buffers"}, h.record(t).PendingRebootParameters)
}
// D16: a class change re-resolves the size-derived defaults and the replacement
// A class change re-resolves the size-derived defaults and the replacement
// VM boots on them, so the record must not report a change that has landed —
// the customer would reboot a healthy database to clear it, and Terraform would
// see it on every plan.
// see it in every configuration read.
func TestApplyPendingModifications_ClassChangeClearsThePendingRebootParameters(t *testing.T) {
h := newModifyHarness(t)
h.agent.replyWith("shared_buffers")
@@ -308,7 +308,7 @@ func TestReconciler_RetriesAFailedModifyInsideTheBudget(t *testing.T) {
assert.Equal(t, int64(50), h.record(t).AllocatedStorage)
}
// D20: the size-derived defaults are the reason a class change has to re-resolve
// The size-derived defaults are the reason a class change has to re-resolve
// rather than carry the old set forward — a shared_buffers computed for the old
// class is wrong at the new one in whichever direction the class moved.
func TestApplyPendingModifications_ReResolvesTheParametersForTheNewClass(t *testing.T) {
+2 -2
View File
@@ -71,7 +71,7 @@ type InstanceStateResolver interface {
// Its responsibilities are the transitions no single API call can finish — the
// ones it drives itself and the ones whose caller died partway through — plus
// the failure classifier that gives a settled instance an honest health state.
// The backup sweep (rds-9) extends the same loop.
// The backup sweep extends the same loop.
type Reconciler struct {
svc *Service
holder string
@@ -436,7 +436,7 @@ func (r *Reconciler) reconcileBackingUp(ctx context.Context, kv jetstream.KeyVal
return r.transition(ctx, kv, rev, rec, resume, "")
}
// The two scheduled passes (rds-9): the automated backup its backup window is
// The two scheduled passes: the automated backup's backup window is
// due, and the deferred modify its maintenance window opens. Both ride the leader
// lease, and both are driven from a persisted stamp rather than a timer, so a
// leader change cannot fire either of them twice for one window.
+14 -14
View File
@@ -24,14 +24,14 @@ type DBInstanceRecord struct {
MasterUsername string `json:"masterUsername"`
Port int64 `json:"port"`
// A one-way marker (D8). A rotated password is handed to the agent over the
// A one-way marker. A rotated password is handed to the agent over the
// command channel and never persisted, so this records only that it changed.
MasterPasswordUpdatedAt *time.Time `json:"masterPasswordUpdatedAt,omitempty"`
// Blocks DeleteDBInstance outright (D18). Settable at create and modify.
// Blocks DeleteDBInstance outright. Settable at create and modify.
DeletionProtection bool `json:"deletionProtection,omitempty"`
// Inert (D19) — the engine version is pinned, so nothing upgrades either way.
// Inert — the engine version is pinned, so nothing upgrades either way.
// Recorded anyway because a describe that does not echo it back reads as
// false against a Terraform schema that defaults it to true, and the plan
// then never settles on a change no modify can deliver.
@@ -106,7 +106,7 @@ type DBInstanceRecord struct {
// When the classifier first observed the instance dark, and the timestamp the
// failure grace window is measured from. Persisted rather than held in leader
// memory so a leader change does not restart the clock. Cleared only by a
// healthy heartbeat or an explicit lifecycle op (D13).
// healthy heartbeat or an explicit lifecycle operation.
UnhealthySince *time.Time `json:"unhealthySince,omitempty"`
// When the lifecycle op that put the instance in its current transitional
@@ -128,7 +128,7 @@ type DBInstanceRecord struct {
RestoredFromDBSnapshot string `json:"restoredFromDbSnapshot,omitempty"`
// Static parameters written to the engine's config but not yet in effect.
// Cleared by the reboot that applies them (D16).
// Cleared by the reboot that applies them.
PendingRebootParameters []string `json:"pendingRebootParameters,omitempty"`
// Inline rather than a separate key space, so the record delete that ends the
@@ -146,10 +146,10 @@ type DBInstanceRecord struct {
// each is recorded before the work starts and cleared as it lands. That makes
// one structure serve both meanings AWS gives PendingModifiedValues — a
// deferred change waiting for its maintenance window, and an in-flight change a
// crashed leader has to be able to finish (D12/D15/D16).
// crashed leader has to be able to finish.
//
// MasterUserPassword is deliberately absent: D8 forbids persisting the
// cleartext, and AWS applies a password change as soon as possible regardless
// MasterUserPassword is deliberately absent: cleartext is never persisted,
// and AWS applies a password change as soon as possible regardless
// of ApplyImmediately, so there is nothing to defer.
type PendingModifiedValues struct {
AllocatedStorage *int64 `json:"allocatedStorage,omitempty"`
@@ -229,7 +229,7 @@ const (
// The snapshot operation holding a DB instance, written under the same CAS that
// moves it to backing-up so a second request is rejected rather than queued. An
// rds-9 automated snapshot and a manual one serialise against each other here.
// An automated snapshot and a manual one serialise against each other here.
type SnapshotOperation struct {
DBSnapshotIdentifier string `json:"dbSnapshotIdentifier"`
// Where the instance goes when the snapshot finishes. Recorded rather than
@@ -282,7 +282,7 @@ type DBSnapshotRecord struct {
// True when the engine was still writing as it was taken, so a restore
// replays WAL. A final snapshot is taken with the engine already down, so it
// is never crash-consistent; rds-8's quiesce fallback is what sets this.
// is never crash-consistent; the quiesce fallback is what sets this.
CrashConsistent bool `json:"crashConsistent,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
@@ -307,8 +307,8 @@ type AutomatedBackupRecord struct {
}
// A data volume that outlived its DB instance because a COW snapshot still
// references its chunks (D10). The last DeleteDBSnapshot to empty Snapshots
// deletes it; rds-9's reaper is the backstop for a crash in between.
// references its chunks. The last DeleteDBSnapshot to empty Snapshots
// deletes it; the retention reaper is the backstop for a crash in between.
type RetainedVolumeRecord struct {
VolumeID string `json:"volumeId"`
AccountID string `json:"accountId"`
@@ -365,7 +365,7 @@ func (r *DBSubnetGroupRecord) SetTags(tags map[string]string) { r.Tags = tags }
// The db-parameter-groups/{name}/meta record. The values themselves live one key
// each under .../params/, so a modify touching one parameter cannot clobber a
// concurrent change to another (D3).
// concurrent change to another.
type DBParameterGroupRecord struct {
Name string `json:"name"`
AccountID string `json:"accountId"`
@@ -386,7 +386,7 @@ func (r *DBParameterGroupRecord) SetTags(tags map[string]string) { r.Tags = tags
// One stored override, at db-parameter-groups/{name}/params/{key}. ApplyMethod
// is the customer's request rather than a fact: whether a change lands live is
// decided by the parameter's own ApplyType (D16).
// decided by the parameter's own ApplyType.
type DBParameterRecord struct {
Name string `json:"name"`
Value string `json:"value"`
+3 -3
View File
@@ -17,7 +17,7 @@ const defaultFailureGrace = HeartbeatInterval
// What the classifier decides about one instance. Reduced from the EKS
// classifier's live/restartable/lost to healthy/failed, because v1.0 RDS reports
// failure rather than repairing it (D13).
// failure rather than repairing it.
type healthVerdict int
const (
@@ -54,7 +54,7 @@ type healthObservation struct {
func classifyHealth(obs healthObservation) healthVerdict {
// Every other state is owned by something already acting on the VM, and
// stopped is legitimate rather than a failure. This is the interlock that
// keeps the classifier from fighting rds-5a/rds-5b over the same VM.
// keeps the classifier from fighting other recovery paths over the same VM.
if obs.status != StatusAvailable && obs.status != StatusFailed {
return verdictSkip
}
@@ -207,7 +207,7 @@ func (r *Reconciler) lastHeartbeat(accountID string, rec *DBInstanceRecord) (tim
// Whether the record's VM is running, fanned out across the fleet. A nil
// resolver answers true, which is what leaves failure undetectable on a node
// with EC2 unwired instead of declaring it on the heartbeat alone (D13).
// with EC2 unwired instead of declaring it on the heartbeat alone.
func (r *Reconciler) vmRunning(ctx context.Context, accountID string, rec *DBInstanceRecord) (bool, error) {
if r.svc.deps.InstanceState == nil || rec.InstanceID == "" {
return true, nil
+6 -6
View File
@@ -11,10 +11,10 @@ import (
)
// The one primitive a class change, a storage grow that rides the same outage,
// and rds-6's auto-recovery all reduce to: stop the engine, terminate the VM,
// launch a fresh one at the target size, re-attach the same data volume (D9)
// and the same persisted customer ENI (D5), and rewrite the rds-system index so
// the superseded agent can no longer authenticate as this instance (D3).
// and automatic recovery all reduce to: stop the engine, terminate the VM,
// launch a fresh one at the target size, re-attach the same data volume and
// persisted customer ENI, and rewrite the rds-system index so the superseded
// agent can no longer authenticate as this instance.
//
// The endpoint is untouched throughout: the customer ENI keeps its address, so
// the DNS record and the serving cert's SANs stay correct and clients reconnect
@@ -122,7 +122,7 @@ func (s *Service) replaceInstanceVM(ctx context.Context, kv jetstream.KeyValue,
// A fresh COW clone of the engine AMI, re-attached to the identity the DB
// instance owns. The new agent fetches its bootstrap config in attach mode
// (D8): it gets the port, the resolved parameters and a freshly minted serving
// It gets the port, the resolved parameters and a freshly minted serving
// cert, but no master password, and rds-init skips initdb on the datadir it
// finds already initialised.
func (s *Service) launchReplacementVM(ctx context.Context, accountID string, rec *DBInstanceRecord, instanceType, profileARN string) (*LaunchOutput, error) {
@@ -155,7 +155,7 @@ func (s *Service) launchReplacementVM(ctx context.Context, accountID string, rec
// The instance index keys off the internal EC2 instance ID, which every replace
// mints anew. The old entry is removed first: while it exists the superseded
// agent's IMDS credentials still resolve to this DB instance (D3).
// agent's IMDS credentials still resolve to this DB instance.
func (s *Service) rewriteInstanceIndex(ctx context.Context, accountID string, rec *DBInstanceRecord, oldInstanceID, newInstanceID string) error {
if oldInstanceID != "" && oldInstanceID != newInstanceID {
if err := s.DeleteInstanceIndex(ctx, oldInstanceID); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
+2 -2
View File
@@ -28,7 +28,7 @@ func seedReplaceable(t *testing.T, h *modifyHarness, rec DBInstanceRecord) DBIns
return rec
}
// D5/D9: the endpoint ENI and the datadir outlive the VM, so a replace adopts
// The endpoint ENI and the datadir outlive the VM, so a replace adopts
// both. Minting either would move the address clients resolve or hand the
// customer an empty database.
func TestReplaceInstanceVM_ReusesTheEndpointENIAndDataVolume(t *testing.T) {
@@ -101,7 +101,7 @@ func TestReplaceInstanceVM_StopsTheEngineAndTerminatesTheOldVMFirst(t *testing.T
assert.Contains(t, h.launch.enis.deleted, "eni-sys01")
}
// D3: while the old index entry exists the superseded agent's IMDS credentials
// While the old index entry exists the superseded agent's IMDS credentials
// still resolve to this DB instance, so it goes before the new one lands.
func TestReplaceInstanceVM_RewritesTheInstanceIndex(t *testing.T) {
h := newModifyHarness(t)
+4 -4
View File
@@ -22,7 +22,7 @@ import (
// instance in every respect but its data: a new identifier, customer ENI, DNS
// name, serving certificate and VM. What it inherits is the datadir — which
// already holds the database, its roles and their password hashes, so the engine
// starts on it rather than running initdb (D10).
// starts on it rather than running initdb.
func (s *Service) RestoreDBInstanceFromDBSnapshot(ctx context.Context, input *rds.RestoreDBInstanceFromDBSnapshotInput, accountID string) (out *rds.RestoreDBInstanceFromDBSnapshotOutput, err error) {
if input == nil {
return nil, awserrors.Errorf(awserrors.ErrorInvalidParameterValue, "empty request")
@@ -236,7 +236,7 @@ func resolveRestoreRequest(input *rds.RestoreDBInstanceFromDBSnapshotInput, snap
Port: port,
MasterUsername: snapshot.MasterUsername,
// Deliberately empty: the datadir already holds the master role and its
// password hash, so there is nothing for a bootstrap to set (D8).
// password hash, so there is nothing for a bootstrap to set.
DBName: snapshot.DBName,
SecurityGroupIDs: securityGroups,
DBSubnetGroupName: subnetGroup,
@@ -298,7 +298,7 @@ func resolveRestorePort(input *rds.RestoreDBInstanceFromDBSnapshotInput, snapsho
return engine.DefaultPort, nil
}
// D19: a parameter that would create a false safety, security or availability
// A parameter that would create a false safety, security or availability
// guarantee is rejected rather than silently dropped. The list mirrors the one
// at create, plus what only a restore can be asked for.
func rejectUnimplementedRestore(input *rds.RestoreDBInstanceFromDBSnapshotInput) error {
@@ -347,7 +347,7 @@ func rejectUnimplementedRestore(input *rds.RestoreDBInstanceFromDBSnapshotInput)
// The reserved record for a restored instance: everything the request and the
// snapshot decided, with the bootstrap marked consumed so the agent's first
// fetch returns attach rather than a password it would use to run initdb (D8).
// fetch returns attach rather than a password it would use to run initdb.
func newRestoredDBInstanceRecord(accountID string, req *validatedCreate, placement *endpointPlacement,
parameters []Parameter, snapshot *DBSnapshotRecord) DBInstanceRecord {
rec := newDBInstanceRecord(accountID, req, placement, parameters)
+2 -2
View File
@@ -61,7 +61,7 @@ func TestRestoreDBInstanceFromDBSnapshot_BuildsANewInstanceOnTheSnapshotsData(t
assert.Equal(t, testSnapshotID, stored.RestoredFromDBSnapshot)
assert.True(t, stored.StorageEncrypted)
// D8: the datadir already holds the master role and its password hash, so
// The datadir already holds the master role and its password hash, so
// the agent's first bootstrap fetch has to attach rather than run initdb.
assert.True(t, stored.Bootstrap.Consumed)
require.NotNil(t, stored.Bootstrap.ConsumedAt)
@@ -201,7 +201,7 @@ func TestRestoreDBInstanceFromDBSnapshot_RejectsASnapshotStillBeingTaken(t *test
assert.Contains(t, err.Error(), awserrors.ErrorDBSnapshotInvalidState)
}
// D19: a parameter that would create a false safety, security or availability
// A parameter that would create a false safety, security or availability
// guarantee is rejected rather than silently dropped.
func TestRestoreDBInstanceFromDBSnapshot_RejectsUnimplementedParameters(t *testing.T) {
h := newSnapshotHarness(t, false)
+2 -2
View File
@@ -37,10 +37,10 @@ type Deps struct {
// Takes the final snapshot at delete, and reports which snapshots still
// reference a data volume before it can be deleted.
Snapshots snapshotProvider
// Grows the data volume behind an AllocatedStorage modify (D12).
// Grows the data volume behind an AllocatedStorage modify.
Storage volumeResizer
// The northstar base zone. Empty means no vanity hostname, and the endpoint
// is the customer-ENI IP instead (D6).
// is the customer-ENI IP instead.
BaseDomain string
// Identifies this node in the reconciler lease.
HolderID string
+10 -10
View File
@@ -20,7 +20,7 @@ import (
// The EC2 snapshot surface the RDS control plane drives. A DB snapshot is an
// ec2.CreateSnapshot of the instance's data volume wrapped in an agent quiesce
// (D10); DescribeSnapshots is also what answers whether a volume is still
// consistent; DescribeSnapshots is also what answers whether a volume is still
// referenced, which is what decides between deleting and retaining it.
type snapshotProvider interface {
CreateSnapshot(ctx context.Context, input *ec2.CreateSnapshotInput, accountID string) (*ec2.Snapshot, error)
@@ -45,7 +45,7 @@ const (
// Takes a customer-requested snapshot of the instance's data volume. The engine
// is held at a checkpoint for the length of it, so the captured datadir is a
// checkpoint rather than a mid-write state — and if it cannot be, the snapshot
// is still taken and reported as crash consistent (D10).
// is still taken and reported as crash consistent.
func (s *Service) CreateDBSnapshot(ctx context.Context, input *rds.CreateDBSnapshotInput, accountID string) (*rds.CreateDBSnapshotOutput, error) {
req, err := validateCreateSnapshotRequest(input)
if err != nil {
@@ -70,8 +70,8 @@ func (s *Service) CreateDBSnapshot(ctx context.Context, input *rds.CreateDBSnaps
return &rds.CreateDBSnapshotOutput{DBSnapshot: s.projectDBSnapshot(record)}, nil
}
// The one snapshot path (D10). A customer's CreateDBSnapshot and an rds-9
// automated backup differ only in who asks and in the type stamped on the record;
// The one snapshot path. A customer's CreateDBSnapshot and an automated
// backup differ only in who asks and in the type stamped on the record;
// the quiesce, the node-addressed EC2 snapshot, the db-snapshots record and the
// per-instance in-flight guard are the same for both.
//
@@ -147,7 +147,7 @@ func (s *Service) snapshotDataVolume(ctx context.Context, accountID string, rec
// graceful stop, which is the checkpoint a quiesce would be forcing.
if resume == StatusAvailable {
if quiesceErr := s.quiesceEngine(ctx, accountID, rec.DBInstanceIdentifier, dbSnapshotIdentifier); quiesceErr != nil {
// D10: still a restorable backup, so it is taken and reported honestly
// Still a restorable backup, so it is taken and reported honestly
// rather than refused. The engine replays WAL on restore.
crashConsistent = true
slog.WarnContext(ctx, "rds: the engine could not be quiesced; taking a crash-consistent snapshot",
@@ -287,7 +287,7 @@ func (s *Service) DeleteDBSnapshot(ctx context.Context, input *rds.DeleteDBSnaps
}
// Removes a DB snapshot, its EC2 data and — when this was the last reference — the
// data volume behind it. Shared with rds-9's retention sweep, which cannot go
// data volume behind it. Shared with the retention sweep, which cannot go
// through DeleteDBSnapshot: that rejects the rds: namespace automated snapshots
// live in, so a customer can never delete one by hand.
func (s *Service) removeDBSnapshot(ctx context.Context, kv jetstream.KeyValue, accountID string, rec *DBSnapshotRecord) error {
@@ -388,7 +388,7 @@ func (s *Service) releaseRetainedVolume(ctx context.Context, kv jetstream.KeyVal
}
// Deletes a retained data volume once nothing holds it, or records the holders it
// still has. Reports whether the volume actually went, which is what lets rds-9's
// still has. Reports whether the volume actually went, which is what lets the
// reaper count the ones it reclaimed.
func (s *Service) reclaimRetainedVolume(ctx context.Context, kv jetstream.KeyValue,
retained *RetainedVolumeRecord) (bool, error) {
@@ -428,7 +428,7 @@ func (s *Service) reclaimRetainedVolume(ctx context.Context, kv jetstream.KeyVal
return true, nil
}
// The request as CreateDBSnapshot resolved it, or as rds-9's scheduler and the
// The request as CreateDBSnapshot resolved it, or as the scheduler and the
// delete path's final-snapshot reservation construct it. SnapshotType is what the
// record is stamped with, and the only thing separating an automated backup from
// a manual snapshot once it exists.
@@ -463,7 +463,7 @@ func validateCreateSnapshotRequest(input *rds.CreateDBSnapshotInput) (*validated
}, nil
}
// Returns the snapshot type to filter on, empty for "any". D19: a filter this
// Returns the snapshot type to filter on, empty for "any". A filter this
// phase cannot honour is rejected rather than dropped, because a silently
// unfiltered list reads as a complete answer.
func validateDescribeSnapshotsRequest(input *rds.DescribeDBSnapshotsInput) (string, error) {
@@ -608,7 +608,7 @@ func (s *Service) discardSnapshotRecord(ctx context.Context, kv jetstream.KeyVal
// Moves the instance into backing-up and records the snapshot holding it in one
// CAS, returning the status the instance goes back to. That write is the
// per-instance guard: a second snapshot, an rds-9 automated one or a lifecycle
// per-instance guard: a second snapshot, an automated one or a lifecycle
// op finds the instance already backing-up and is rejected rather than queued.
func (s *Service) beginSnapshotOperation(ctx context.Context, kv jetstream.KeyValue, rev uint64,
rec *DBInstanceRecord, dbSnapshotIdentifier string) (Status, error) {
+2 -2
View File
@@ -205,7 +205,7 @@ func TestCreateDBSnapshot_DoesNotQuiesceAStoppedInstance(t *testing.T) {
assert.False(t, stored.CrashConsistent)
}
// D10: a snapshot that could not be quiesced is still a restorable backup, so it
// A snapshot that could not be quiesced is still a restorable backup, so it
// is taken and reported honestly rather than refused.
func TestCreateDBSnapshot_FallsBackToCrashConsistentWhenTheQuiesceFails(t *testing.T) {
h := newSnapshotHarness(t, true)
@@ -334,7 +334,7 @@ func TestDescribeDBSnapshots_NamedSnapshotThatDoesNotExistIsAnError(t *testing.T
assert.Contains(t, err.Error(), awserrors.ErrorDBSnapshotNotFound)
}
// D19: a filter this phase cannot honour is rejected, because a silently
// A filter this phase cannot honour is rejected, because a silently
// unfiltered list reads as a complete answer.
func TestDescribeDBSnapshots_RejectsUnhonouredScoping(t *testing.T) {
h := newSnapshotHarness(t, false)

Some files were not shown because too many files have changed in this diff Show More