mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Block hosted GA on production storage pressure
This commit is contained in:
parent
0f9437227f
commit
0a8d3586c6
10 changed files with 222 additions and 6 deletions
|
|
@ -0,0 +1,104 @@
|
|||
# Cloud Hosted Tier Runtime Readiness Storage Blocker
|
||||
|
||||
- Date: `2026-04-23`
|
||||
- Gate: `cloud-hosted-tier-runtime-readiness`
|
||||
- Assertion: `RA11`
|
||||
- Result: `blocked`
|
||||
- Environment:
|
||||
- Live control plane: `https://cloud.pulserelay.pro`
|
||||
- Host: `root@pulse-cloud`
|
||||
- Registry DB: `/data/control-plane/tenants.db`
|
||||
- Tenant runtime root: `/data/tenants`
|
||||
|
||||
## Blocking Facts
|
||||
|
||||
1. A fresh live MSP rehearsal was intentionally stopped before creating the
|
||||
canary account or workspaces because the production host root filesystem was
|
||||
full:
|
||||
- `/dev/vda1` mounted at `/` reported `154G` used of `154G`
|
||||
- inode usage was only `6%`, so this is block exhaustion rather than inode
|
||||
exhaustion
|
||||
2. The space pressure is dominated by runtime/build retention, not tenant data:
|
||||
- `/var/lib/containerd`: `109G`
|
||||
- `/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs`: `99G`
|
||||
- `/var/lib/containerd/io.containerd.content.v1.content`: `10G`
|
||||
- `/var/lib/docker`: `8.5G`
|
||||
- `/var/lib/docker/containers`: `8.1G`
|
||||
- `/tmp`: `34G`
|
||||
- `/data`: `846M`
|
||||
3. Docker reported substantial reclaimable runtime/build state:
|
||||
- images: `116.2GB` total, `109.9GB` reclaimable
|
||||
- containers: `52.36GB` total, `6.828GB` reclaimable
|
||||
- build cache: `21.28GB` total, `15.48GB` private/reclaimable
|
||||
4. The live control-plane registry currently carries a standing hosted fleet
|
||||
rather than an empty production system:
|
||||
- tenant states: `77 active`, `3 canceled`, `4 deleted`, `1 suspended`
|
||||
- accounts: `97` total (`93 individual`, `4 msp`)
|
||||
- active plan distribution included `50` `v5_pro_annual_grandfathered`,
|
||||
`13` `msp_starter`, `10` `v5_pro_monthly_grandfathered`, and `4`
|
||||
`cloud_starter`
|
||||
5. Docker runtime state showed `93` containers, `83` running, and `81` unhealthy.
|
||||
Sampled tenant health checks were failing with:
|
||||
- `OCI runtime exec failed: write /tmp/runc-process...: no space left on device`
|
||||
6. Docker host-level retention policy is absent:
|
||||
- `/etc/docker/daemon.json` was not present
|
||||
- tenant containers use Docker's default `json-file` log driver with no
|
||||
`max-size` or `max-file`
|
||||
- no Docker/container prune timer was present in `systemctl list-timers`
|
||||
7. Container JSON logs are already materially contributing to the issue:
|
||||
individual tenant log files were sampled at roughly `140M`-`185M`, with no
|
||||
configured rotation.
|
||||
8. Historical build artifacts remain on the production host under `/tmp`, with
|
||||
old Pulse build/source directories contributing tens of gigabytes, including
|
||||
multi-gigabyte directories from March 2026.
|
||||
|
||||
## Why The Gate Cannot Be Treated As Passed
|
||||
|
||||
The previously recorded hosted production proofs remain valuable point-in-time
|
||||
functional evidence, but the current live production environment no longer
|
||||
meets the operational floor needed for GA. A customer-facing hosted tier cannot
|
||||
be called ready while tenant health checks fail from disk exhaustion, old proof
|
||||
tenants remain as a standing production fleet, and the host has no Docker
|
||||
retention or log-bounding policy.
|
||||
|
||||
This also blocks the requested fresh MSP production rehearsal. Running new
|
||||
workspace provisioning into a full root filesystem would produce misleading
|
||||
evidence and risk additional production damage.
|
||||
|
||||
## Required Unblock Steps
|
||||
|
||||
1. Immediate live-host containment, with explicit destructive-action approval:
|
||||
- remove stale Pulse build/source directories from `/tmp`
|
||||
- remove stopped pre-rollout tenant containers
|
||||
- prune old build cache and unused images
|
||||
- classify the active hosted proof/canary tenant fleet before deprovisioning
|
||||
or retaining any tenant
|
||||
2. Add a production retention policy:
|
||||
- Docker log rotation for tenant containers
|
||||
- scheduled Docker/containerd build-cache and unused-image cleanup
|
||||
- documented canary/proof tenant lifecycle and cleanup ownership
|
||||
3. Add production storage guardrails:
|
||||
- disk pressure monitoring and alerting for `/`, `/var/lib/containerd`,
|
||||
`/var/lib/docker`, `/tmp`, and `/data`
|
||||
- provisioning admission should fail closed before creating a tenant when
|
||||
required runtime storage is below the safe threshold
|
||||
4. Move live release builds away from persistent production `/tmp` state and
|
||||
favor digest-pinned images built outside the production runtime host.
|
||||
5. After cleanup and guardrails are in place, rerun the fresh production MSP
|
||||
rehearsal and hosted-runtime proof from a new canary.
|
||||
|
||||
## Conclusion
|
||||
|
||||
`cloud-hosted-tier-runtime-readiness` is blocked again as of `2026-04-23`.
|
||||
The code-level MSP readiness fixes are still useful, but the live hosted
|
||||
production environment is not GA-ready until this storage and retention issue is
|
||||
fixed and re-proven.
|
||||
|
||||
## Immediate Repo Containment
|
||||
|
||||
The control-plane Docker manager now creates new tenant runtime containers with
|
||||
bounded `json-file` logs (`CP_TENANT_LOG_MAX_SIZE`, default `10m`, and
|
||||
`CP_TENANT_LOG_MAX_FILE`, default `3`). This prevents future tenant containers
|
||||
from accumulating unbounded Docker JSON logs after the fix is deployed and
|
||||
tenant containers are recreated, but it does not reclaim the existing production
|
||||
host or replace the required live-host cleanup steps above.
|
||||
|
|
@ -915,6 +915,11 @@
|
|||
"path": "docs/release-control/v6/internal/records/cloud-hosted-tier-runtime-readiness-production-recovered-2026-03-26.md",
|
||||
"kind": "file"
|
||||
},
|
||||
{
|
||||
"repo": "pulse",
|
||||
"path": "docs/release-control/v6/internal/records/cloud-hosted-tier-runtime-readiness-storage-blocker-2026-04-23.md",
|
||||
"kind": "file"
|
||||
},
|
||||
{
|
||||
"repo": "pulse",
|
||||
"path": "docs/release-control/v6/internal/subsystems/cloud-paid.md",
|
||||
|
|
@ -3434,7 +3439,7 @@
|
|||
"owner": "project-owner",
|
||||
"blocking_level": "rc-ready",
|
||||
"minimum_evidence_tier": "real-external-e2e",
|
||||
"status": "passed",
|
||||
"status": "blocked",
|
||||
"verification_doc": "docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md",
|
||||
"lane_ids": [
|
||||
"L3",
|
||||
|
|
@ -3478,6 +3483,12 @@
|
|||
"path": "docs/release-control/v6/internal/records/cloud-hosted-tier-runtime-readiness-production-recovered-2026-03-26.md",
|
||||
"kind": "file",
|
||||
"evidence_tier": "real-external-e2e"
|
||||
},
|
||||
{
|
||||
"repo": "pulse",
|
||||
"path": "docs/release-control/v6/internal/records/cloud-hosted-tier-runtime-readiness-storage-blocker-2026-04-23.md",
|
||||
"kind": "file",
|
||||
"evidence_tier": "real-external-e2e"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ cloud-specific enforcement rules.
|
|||
7. `internal/cloudcp/auth/magiclink_store.go` shared with `security-privacy`: control-plane magic-link persistence is both a Pulse Cloud account-access boundary and a security/privacy storage-hardening boundary.
|
||||
8. `internal/cloudcp/docker/labels.go` shared with `deployment-installability`: hosted tenant Docker labels are both a Pulse Cloud runtime contract boundary and a deployment-installability rollout boundary.
|
||||
9. `internal/cloudcp/docker/manager.go` shared with `deployment-installability`: hosted tenant container management is both a Pulse Cloud runtime contract boundary and a deployment-installability rollout boundary.
|
||||
Hosted tenant container creation must also bound Docker `json-file` logs
|
||||
through the control-plane Docker manager so tenant runtime logging cannot
|
||||
fill the live Pulse Cloud host independently of tenant data quotas.
|
||||
10. `internal/cloudcp/tenant_runtime_rollout.go` shared with `deployment-installability`: hosted tenant runtime rollout is both a Pulse Cloud runtime contract boundary and a deployment-installability release-rollout boundary.
|
||||
|
||||
The real `pulse-pro` license-server legacy checkout issuance, recurring
|
||||
|
|
@ -168,7 +171,10 @@ Community limit enforcement.
|
|||
2. Add or change hosted entitlement issuance through `internal/cloudcp/entitlements/service.go`
|
||||
3. Add or change control-plane plan storage through `internal/cloudcp/registry/models.go` and `internal/cloudcp/registry/registry.go`
|
||||
4. Add or change MSP account-scoped workspace provisioning entry handlers through `internal/cloudcp/account/tenant_handlers.go`
|
||||
5. Add or change public cloud self-serve signup price configuration or checkout gating through `internal/cloudcp/config.go` and `internal/cloudcp/public_cloud_signup_handlers.go`
|
||||
5. Add or change public cloud self-serve signup price configuration,
|
||||
tenant-runtime capacity/log retention configuration, or checkout gating
|
||||
through `internal/cloudcp/config.go` and
|
||||
`internal/cloudcp/public_cloud_signup_handlers.go`
|
||||
6. Add or change the hosted account portal API, Pulse Account access/auth/session handling, task-first browser shell, maintained portal frontend/bundle, or account-scoped workspace/access/billing handoff through `internal/cloudcp/account/audit.go`, `internal/cloudcp/account/handlers.go`, `internal/cloudcp/auth/handlers.go`, `internal/cloudcp/auth/session.go`, `internal/cloudcp/portal/`, and `internal/cloudcp/routes.go`
|
||||
That same customer-entry boundary owns the canonical hosted Cloud handoff:
|
||||
public Cloud entry, secure checkout return, and returning-customer sign-in
|
||||
|
|
|
|||
|
|
@ -100,6 +100,9 @@ server-side update execution surfaces.
|
|||
2. `internal/api/updates.go` shared with `api-contracts`: update handlers are both a deployment-installability control surface and a canonical API payload contract boundary.
|
||||
3. `internal/cloudcp/docker/labels.go` shared with `cloud-paid`: hosted tenant Docker labels are both a Pulse Cloud runtime contract boundary and a deployment-installability rollout boundary.
|
||||
4. `internal/cloudcp/docker/manager.go` shared with `cloud-paid`: hosted tenant container management is both a Pulse Cloud runtime contract boundary and a deployment-installability rollout boundary.
|
||||
Tenant runtime containers must be created with bounded Docker `json-file`
|
||||
logging so rollout and canary fleets cannot consume unbounded production
|
||||
host storage while they remain running.
|
||||
5. `internal/cloudcp/tenant_runtime_rollout.go` shared with `cloud-paid`: hosted tenant runtime rollout is both a Pulse Cloud runtime contract boundary and a deployment-installability release-rollout boundary.
|
||||
6. `scripts/install.ps1` shared with `agent-lifecycle`: the Windows installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary.
|
||||
7. `scripts/install.sh` shared with `agent-lifecycle`: the shell installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary.
|
||||
|
|
@ -107,7 +110,7 @@ server-side update execution surfaces.
|
|||
## Extension Points
|
||||
|
||||
1. Add or change deployment-type detection, update planning, or apply behavior through `internal/updates/`
|
||||
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, and `.github/workflows/update-demo-server.yml`
|
||||
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, download endpoint checksum/signature header proof, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, and `.github/workflows/update-demo-server.yml`
|
||||
3. Add or change shell installer, Docker bootstrap installer, Windows installer, container-agent installer, repo-root compose defaults, or auto-update script behavior through `scripts/install.sh`, `scripts/install-docker.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, `docker-compose.yml`, and `scripts/pulse-auto-update.sh`
|
||||
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `scripts/dev-deploy-agent.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/run_demo_public_browser_smoke.sh`, `scripts/demo_public_browser_smoke.cjs`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`
|
||||
|
|
@ -175,12 +178,20 @@ server-side update execution surfaces.
|
|||
External helper binaries fetched by governed release workflows are part of
|
||||
the same supply-chain boundary and must be checksum-verified before they are
|
||||
executed.
|
||||
Release validation must prove that installer script download endpoints return
|
||||
signature headers, and unified-agent download endpoints must return checksum and signature headers whose checksum value matches the served binary.
|
||||
8. Add or change the non-secret Pulse Cloud public signup route smoke through
|
||||
`scripts/run_cloud_public_signup_smoke.sh`. That smoke must prove either
|
||||
the open signup route contract or the intentionally closed redirect contract,
|
||||
and valid magic-link probes must remain opt-in so routine public checks do
|
||||
not send email accidentally.
|
||||
9. Add or change operator-facing hosted tenant runtime canary rollout, batch runtime contract reconciliation, canonical hosted route/public URL generation, or control-plane runtime-registry reconciliation through `cmd/pulse-control-plane/main.go`, `internal/cloudcp/docker/manager.go`, `internal/cloudcp/docker/labels.go`, and `internal/cloudcp/tenant_runtime_rollout.go`
|
||||
9. Add or change operator-facing hosted tenant runtime canary rollout, tenant
|
||||
runtime container log-retention bounds, batch runtime contract
|
||||
reconciliation, canonical hosted route/public URL generation, or
|
||||
control-plane runtime-registry reconciliation through
|
||||
`cmd/pulse-control-plane/main.go`, `internal/cloudcp/docker/manager.go`,
|
||||
`internal/cloudcp/docker/labels.go`, and
|
||||
`internal/cloudcp/tenant_runtime_rollout.go`
|
||||
10. Add or change the canonical hosted staging smoke operator path through `scripts/run_hosted_staging_smoke.sh`, `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`, `tests/integration/scripts/hosted-mobile-token-runtime.mjs`, `tests/integration/scripts/hosted-tenant-runtime.mjs`, and `tests/integration/scripts/relay-mobile-token-helper.go`
|
||||
|
||||
## Forbidden Paths
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ type CPConfig struct {
|
|||
TrustedProxyCIDRs []string
|
||||
TenantMemoryLimit int64 // bytes
|
||||
TenantCPUShares int64
|
||||
TenantLogMaxSize string
|
||||
TenantLogMaxFile int
|
||||
AllowDockerlessProvisioning bool
|
||||
StripeWebhookSecret string
|
||||
StripeAPIKey string
|
||||
|
|
@ -79,6 +81,10 @@ func LoadConfig() (*CPConfig, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantLogMaxFile, err := envOrDefaultInt("CP_TENANT_LOG_MAX_FILE", 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhookRPS, err := envOrDefaultInt("CP_RL_WEBHOOK_PER_MINUTE", 120)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -124,6 +130,8 @@ func LoadConfig() (*CPConfig, error) {
|
|||
TrustedProxyCIDRs: parseTrustedProxyCIDRValues("CP_TRUSTED_PROXY_CIDRS", "PULSE_TRUSTED_PROXY_CIDRS"),
|
||||
TenantMemoryLimit: tenantMemoryLimit,
|
||||
TenantCPUShares: tenantCPUShares,
|
||||
TenantLogMaxSize: envOrDefault("CP_TENANT_LOG_MAX_SIZE", "10m"),
|
||||
TenantLogMaxFile: tenantLogMaxFile,
|
||||
AllowDockerlessProvisioning: envOrDefaultBool("CP_ALLOW_DOCKERLESS_PROVISIONING", false),
|
||||
StripeWebhookSecret: strings.TrimSpace(os.Getenv("STRIPE_WEBHOOK_SECRET")),
|
||||
StripeAPIKey: strings.TrimSpace(os.Getenv("STRIPE_API_KEY")),
|
||||
|
|
@ -188,6 +196,12 @@ func (c *CPConfig) validate() error {
|
|||
if c.TenantCPUShares <= 0 {
|
||||
return fmt.Errorf("CP_TENANT_CPU_SHARES must be greater than 0, got %d", c.TenantCPUShares)
|
||||
}
|
||||
if strings.TrimSpace(c.TenantLogMaxSize) == "" {
|
||||
return fmt.Errorf("CP_TENANT_LOG_MAX_SIZE must not be empty")
|
||||
}
|
||||
if c.TenantLogMaxFile <= 0 {
|
||||
return fmt.Errorf("CP_TENANT_LOG_MAX_FILE must be greater than 0")
|
||||
}
|
||||
if c.WebhookRateLimitPerMinute <= 0 {
|
||||
return fmt.Errorf("CP_RL_WEBHOOK_PER_MINUTE must be greater than 0")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ func TestLoadConfig_AllRequired(t *testing.T) {
|
|||
if cfg.BindAddress != "0.0.0.0" {
|
||||
t.Errorf("BindAddress = %q, want 0.0.0.0", cfg.BindAddress)
|
||||
}
|
||||
if cfg.TenantLogMaxSize != "10m" {
|
||||
t.Errorf("TenantLogMaxSize = %q, want 10m", cfg.TenantLogMaxSize)
|
||||
}
|
||||
if cfg.TenantLogMaxFile != 3 {
|
||||
t.Errorf("TenantLogMaxFile = %d, want 3", cfg.TenantLogMaxFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_CustomValues(t *testing.T) {
|
||||
|
|
@ -78,6 +84,8 @@ func TestLoadConfig_CustomValues(t *testing.T) {
|
|||
t.Setenv("CP_PORT", "9000")
|
||||
t.Setenv("CP_DATA_DIR", "/custom/data")
|
||||
t.Setenv("CP_BIND_ADDRESS", "127.0.0.1")
|
||||
t.Setenv("CP_TENANT_LOG_MAX_SIZE", "25m")
|
||||
t.Setenv("CP_TENANT_LOG_MAX_FILE", "4")
|
||||
|
||||
cfg, err := LoadConfig()
|
||||
if err != nil {
|
||||
|
|
@ -92,6 +100,12 @@ func TestLoadConfig_CustomValues(t *testing.T) {
|
|||
if cfg.BindAddress != "127.0.0.1" {
|
||||
t.Errorf("BindAddress = %q", cfg.BindAddress)
|
||||
}
|
||||
if cfg.TenantLogMaxSize != "25m" {
|
||||
t.Errorf("TenantLogMaxSize = %q", cfg.TenantLogMaxSize)
|
||||
}
|
||||
if cfg.TenantLogMaxFile != 4 {
|
||||
t.Errorf("TenantLogMaxFile = %d, want 4", cfg.TenantLogMaxFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_DerivesTrialActivationPublicKey(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ type ManagerConfig struct {
|
|||
TrustedProxyCIDRs []string
|
||||
MemoryLimit int64 // bytes
|
||||
CPUShares int64
|
||||
TenantLogMaxSize string
|
||||
TenantLogMaxFile int
|
||||
ContainerPort int // port inside the container (default 7655)
|
||||
}
|
||||
|
||||
|
|
@ -53,8 +55,10 @@ type RuntimeContainerInfo struct {
|
|||
const immutableOwnershipPathsEnv = "PULSE_IMMUTABLE_OWNERSHIP_PATHS"
|
||||
|
||||
const (
|
||||
tenantRuntimeUID = 1000
|
||||
tenantRuntimeGID = 1000
|
||||
tenantRuntimeUID = 1000
|
||||
tenantRuntimeGID = 1000
|
||||
defaultTenantLogMaxSize = "10m"
|
||||
defaultTenantLogMaxFile = 3
|
||||
)
|
||||
|
||||
// NewManager creates a Docker manager connected to the local daemon.
|
||||
|
|
@ -124,6 +128,7 @@ func (m *Manager) CreateAndStart(ctx context.Context, tenantID, tenantDataDir st
|
|||
},
|
||||
HostConfig: &container.HostConfig{
|
||||
RestartPolicy: container.RestartPolicy{Name: "unless-stopped"},
|
||||
LogConfig: tenantRuntimeLogConfig(m.cfg.TenantLogMaxSize, m.cfg.TenantLogMaxFile),
|
||||
Resources: container.Resources{
|
||||
Memory: m.cfg.MemoryLimit,
|
||||
CPUShares: m.cfg.CPUShares,
|
||||
|
|
@ -154,6 +159,23 @@ func (m *Manager) CreateAndStart(ctx context.Context, tenantID, tenantDataDir st
|
|||
return resp.ID, nil
|
||||
}
|
||||
|
||||
func tenantRuntimeLogConfig(maxSize string, maxFile int) container.LogConfig {
|
||||
maxSize = strings.TrimSpace(maxSize)
|
||||
if maxSize == "" {
|
||||
maxSize = defaultTenantLogMaxSize
|
||||
}
|
||||
if maxFile <= 0 {
|
||||
maxFile = defaultTenantLogMaxFile
|
||||
}
|
||||
return container.LogConfig{
|
||||
Type: "json-file",
|
||||
Config: map[string]string{
|
||||
"max-size": maxSize,
|
||||
"max-file": fmt.Sprintf("%d", maxFile),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func tenantImmutableOwnershipPaths() []string {
|
||||
return []string{
|
||||
"/etc/pulse/secrets/handoff.key",
|
||||
|
|
|
|||
|
|
@ -146,6 +146,29 @@ func TestTenantEnvOmitsPublicURLWithoutTenantContext(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTenantRuntimeLogConfigBoundsJSONLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := tenantRuntimeLogConfig("", 0)
|
||||
if got.Type != "json-file" {
|
||||
t.Fatalf("LogConfig.Type = %q, want json-file", got.Type)
|
||||
}
|
||||
if got.Config["max-size"] != defaultTenantLogMaxSize {
|
||||
t.Fatalf("max-size = %q, want %q", got.Config["max-size"], defaultTenantLogMaxSize)
|
||||
}
|
||||
if got.Config["max-file"] != "3" {
|
||||
t.Fatalf("max-file = %q, want 3", got.Config["max-file"])
|
||||
}
|
||||
|
||||
custom := tenantRuntimeLogConfig("25m", 4)
|
||||
if custom.Config["max-size"] != "25m" {
|
||||
t.Fatalf("custom max-size = %q, want 25m", custom.Config["max-size"])
|
||||
}
|
||||
if custom.Config["max-file"] != "4" {
|
||||
t.Fatalf("custom max-file = %q, want 4", custom.Config["max-file"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalTrustedProxyCIDR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ func Run(ctx context.Context, version string) error {
|
|||
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
|
||||
MemoryLimit: cfg.TenantMemoryLimit,
|
||||
CPUShares: cfg.TenantCPUShares,
|
||||
TenantLogMaxSize: cfg.TenantLogMaxSize,
|
||||
TenantLogMaxFile: cfg.TenantLogMaxFile,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Docker unavailable — container management disabled")
|
||||
|
|
|
|||
|
|
@ -121,6 +121,15 @@ func TestTenantRuntimeRollout_RollsForwardCanonically(t *testing.T) {
|
|||
if docker.renameCalls[0].newName != "pulse-t-ROLLFWD.pre-aliasfix" {
|
||||
t.Fatalf("rename target = %q, want pulse-t-ROLLFWD.pre-aliasfix", docker.renameCalls[0].newName)
|
||||
}
|
||||
if len(docker.createCalls) != 1 {
|
||||
t.Fatalf("create call count = %d, want 1", len(docker.createCalls))
|
||||
}
|
||||
if docker.createCalls[0].tenantID != tenant.ID {
|
||||
t.Fatalf("created tenant id = %q, want %q", docker.createCalls[0].tenantID, tenant.ID)
|
||||
}
|
||||
if docker.createCalls[0].tenantDataDir != filepath.Join(tTempDirForRolloutService(), tenant.ID) {
|
||||
t.Fatalf("created tenant data dir = %q", docker.createCalls[0].tenantDataDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantRuntimeRollout_RollsBackOnHealthFailure(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue