You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
InstanceHA robustness and evacuation tunability improvements
Harden the evacuation pipeline against edge cases in the Nova API and
add three operator-facing knobs that control evacuation behavior under
load.
Robustness fixes:
- Handle Nova service objects missing the forced_down attribute
instead of crashing with AttributeError during service processing.
- Guard against Nova returning None from the migrations API during
host re-enabling, which surfaces when the API is temporarily
unavailable after a compute recovery.
- Replace speculative log messages ("might be …", "possibly …") with
actionable operator guidance that names what to check and why.
Evacuation tunables (new config keys):
- EVACUATION_TIMEOUT (default 300s, range 60-3600): per-server
evacuation timeout. The migration query window auto-adjusts so
_monitor_evacuation never misses in-flight migrations.
- EVACUATION_STAGGER (default 0s, range 0-60): delay injected between
concurrent per-server evacuations to reduce control plane pressure
during mass evacuation.
- EVACUATION_MAX_THREADS (default 32, range 1-512): global thread
budget divided across per-host worker pools, capping the total
concurrent evacuations system-wide.
Timeout accounting for stagger:
- Fix _run_concurrent: move timeout from dead-code future.result()
to as_completed() for actual wall-clock deadline enforcement, and
log which items were still pending when the timeout fires.
- Include stagger budget in _concurrent_evacuate inner timeout,
_process_stale_services outer timeout, and _filter_processing_hosts
processing entry expiry to prevent premature timeouts and duplicate
host processing when stagger is configured.
- Log worst-case last-VM stagger delay for operator visibility.
Scale improvements:
- Raise WORKERS max from 50 to 100 for rack-scale failures at 1000+
compute clusters.
- Raise EVACUATION_MAX_THREADS max from 256 to 512 to match large
Nova control plane capacity.
- Raise MAX_HOSTS_PER_CYCLE max from 100 to 200 to handle multi-rack
failures in a single poll cycle.
Other changes:
- Restructure config.yaml into logical sections with inline comments
and fix inaccurate default values.
- Add tests for non-zero stagger: delay assignment, timeout
calculation, per-phase stagger, shutdown during stagger, and
processing entry expiry with stagger budget.
- Fix hand-rolled mock configs in tests that were missing
EVACUATION_TIMEOUT and EVACUATION_STAGGER, causing TypeError in
_filter_processing_hosts and _concurrent_evacuate.
- Update architecture and guide docs with the new tunables.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
│ If every active service (>=3) still appears stale after heartbeat filtering -> skip ALL fencing
699
-
│ Rationale: all hosts down at once is almost certainly a Nova API or
701
+
│ Rationale: all hosts down at once indicates a Nova API or
700
702
│ infrastructure anomaly, not a genuine failure of every server.
701
703
│ Runs after heartbeat filtering so heartbeat data can disambiguate partial
702
704
│ failures (e.g., nova-compute crash) from Nova API issues
@@ -742,7 +744,7 @@ When `CHECK_HEARTBEAT=false`, gates 2 and 3 are not evaluated and gate 4 (all-se
742
744
|------|-------------------|-----------|
743
745
| K8s API (startup) | Fail-open (allow fencing) | No data yet -- blocking fencing on startup would silently prevent all evacuations until the health check thread runs |
744
746
| K8s API (after 3 failures) | Fail-closed (block fencing) | Confirmed partition -- fencing would hit healthy hosts |
745
-
| All-services-stale | Fail-closed (block fencing) | Every active host appearing stale at once is almost certainly a data-source anomaly, not genuine mass failure |
747
+
| All-services-stale | Fail-closed (block fencing) | Every active host appearing stale at once indicates a data-source anomaly, not genuine mass failure |
746
748
| Heartbeat (grace period) | Fail-open (allow fencing) | No heartbeat history in first `HEARTBEAT_TIMEOUT` seconds -- cannot distinguish "no heartbeat" from "never received one" |
747
749
| Heartbeat (steady state) | Fail-open (allow fencing) | No heartbeat = host is down = proceed with fencing |
748
750
| Cliff detection | Fail-closed (block fencing), expires after `HEARTBEAT_CLIFF_MAX_CYCLES`| Mass heartbeat loss is treated as observer failure; expiry prevents permanent paralysis |
- Target host retry: if evacuation to a specific host fails with 409/500, retries without target (scheduler picks)
1016
1018
- Migration failures trigger automatic re-issue (reset state + new evacuate call)
1017
1019
- API errors retried independently (budget of 10, not shared with migration errors)
1018
-
- Times out after 300 seconds
1020
+
- Times out after `EVACUATION_TIMEOUT` seconds (default 300, configurable)
1019
1021
- Skips redundant state restore when Nova already preserved the original state after evacuation
1020
1022
- Restores original VM state after successful evacuation only when needed (`SHUTOFF` -> stop, `ERROR` -> reset_state)
1021
1023
- Unlocks the server in `finally` block (even on failure)
@@ -2303,7 +2305,7 @@ config:
2303
2305
2304
2306
### Why the Defaults Are Conservative
2305
2307
2306
-
The default `THRESHOLD: 50` and `HEARTBEAT_CLIFF_THRESHOLD: 50` are deliberately conservative because:
2308
+
The default `THRESHOLD: 50` and `HEARTBEAT_CLIFF_THRESHOLD: 50` are conservative because:
2307
2309
- False positives (unnecessary fencing) are more dangerous than false negatives (delayed evacuation)
2308
2310
- On larger clusters, losing >50% of nodes simultaneously almost always indicates a network partition, not real failures
2309
2311
- Small clusters are the exception, not the rule, in production OpenStack deployments
@@ -2444,16 +2446,16 @@ config:
2444
2446
#### WORKERS
2445
2447
**Type**: Integer
2446
2448
**Default**: `4`
2447
-
**Range**: `1-50`
2449
+
**Range**: `1-100`
2448
2450
2449
2451
**Description**: Thread pool size for concurrent processing. Controls parallelism at two levels: host-level processing and per-host server evacuation.
2450
2452
2451
2453
**Usage**:
2452
2454
- **Host-level**: `_process_stale_services` uses `_run_concurrent` with `WORKERS` threads to process multiple failed hosts in parallel via `process_service`. This applies to all evacuation modes (traditional, smart, and orchestrated).
2453
-
- **Per-host (smart/orchestrated only)**: Within each host's evacuation, `_smart_evacuate` and `_orchestrated_evacuate` use `_run_concurrent` to evacuate multiple VMs concurrently. The per-host thread count is calculated as `MAX_TOTAL_EVACUATION_THREADS (32) // WORKERS` to cap the total system-wide thread count.
2455
+
- **Per-host (smart/orchestrated only)**: Within each host's evacuation, `_smart_evacuate` and `_orchestrated_evacuate` use `_run_concurrent` to evacuate multiple VMs concurrently. The per-host thread count is calculated as `EVACUATION_MAX_THREADS // WORKERS` to cap the total system-wide thread count.
2454
2456
2455
2457
**Testing**:
2456
-
- Unit tests verify range validation (1-50)
2458
+
- Unit tests verify range validation (1-100)
2457
2459
- Functional tests use custom values (6, 8) to test concurrent evacuations
2458
2460
- Performance tests measure evacuation throughput with different worker counts
2459
2461
- Test files: `test_instanceha.py:191`, functional tests
@@ -2464,7 +2466,7 @@ config:
2464
2466
WORKERS: 8 # Process 8 hosts concurrently; each host gets up to 4 per-host threads (32/8)
2465
2467
```
2466
2468
2467
-
**Notes**: Applies to all evacuation modes at the host level. For smart/orchestrated mode, also controls per-host server concurrency (capped by `MAX_TOTAL_EVACUATION_THREADS=32`).
2469
+
**Notes**: Applies to all evacuation modes at the host level. For smart/orchestrated mode, also controls per-host server concurrency (capped by `EVACUATION_MAX_THREADS`, default 32).
2468
2470
2469
2471
---
2470
2472
@@ -2576,6 +2578,7 @@ config:
2576
2578
**Notes**:
2577
2579
- Smart mode increases API load (polling) and memory usage (thread pool)
2578
2580
- Traditional mode does not track completion or verify success after submission
2581
+
- When `EVACUATION_STAGGER` > 0, each VM's evacuation start is delayed by `index * stagger` seconds within a phase, reducing control plane pressure during mass evacuation
2579
2582
2580
2583
---
2581
2584
@@ -2657,6 +2660,35 @@ This would skip servers named `test-vm-1`, `dev-instance-02`, etc.
2657
2660
2658
2661
---
2659
2662
2663
+
#### EVACUATION_MAX_THREADS
2664
+
**Type**: Integer
2665
+
**Default**: `32`
2666
+
**Range**: 1-512
2667
+
2668
+
**Description**: Total evacuation thread budget shared across all concurrent host evacuations. The per-host evacuation concurrency is derived as `inner_workers = EVACUATION_MAX_THREADS / WORKERS`. Increase this to allow more concurrent per-host evacuations (faster recovery when a single host with many VMs fails); decrease to reduce control plane pressure.
2669
+
2670
+
**Usage**:
2671
+
- Used in `_concurrent_evacuate()` to compute `inner_workers`: `max(1, EVACUATION_MAX_THREADS // WORKERS)`
2672
+
- Only affects smart evacuation (`SMART_EVACUATION=true`) and orchestrated evacuation (`ORCHESTRATED_RESTART=true`)
2673
+
- Traditional (fire-and-forget) evacuation is single-threaded and not affected
EVACUATION_MAX_THREADS: 100 # With WORKERS=10, gives 10 concurrent evacuations per host
2688
+
```
2689
+
2690
+
---
2691
+
2660
2692
#### EVACUATION_RETRIES
2661
2693
**Type**: Integer
2662
2694
**Default**: `5`
@@ -2667,7 +2699,7 @@ This would skip servers named `test-vm-1`, `dev-instance-02`, etc.
2667
2699
**Usage**:
2668
2700
- Used in `_monitor_evacuation()` to cap migration-error retries
2669
2701
- On each failure the server state is reset to `error` and evacuation is re-issued
2670
-
- Does not affect API-error retries (`MAX_API_RETRIES`) or the overall evacuation timeout (`MAX_EVACUATION_TIMEOUT_SECONDS`)
2702
+
- Does not affect API-error retries (`MAX_API_RETRIES`) or the overall evacuation timeout (`EVACUATION_TIMEOUT`)
2671
2703
2672
2704
**Example**:
2673
2705
```yaml
@@ -2677,6 +2709,50 @@ config:
2677
2709
2678
2710
---
2679
2711
2712
+
#### EVACUATION_TIMEOUT
2713
+
**Type**: Integer (seconds)
2714
+
**Default**: `300`
2715
+
**Range**: 60-3600
2716
+
2717
+
**Description**: Maximum wall-clock time (in seconds) to monitor a single instance evacuation before giving up. Increase this for environments with large VMs or slow storage backends where evacuations routinely exceed 5 minutes.
2718
+
2719
+
**Usage**:
2720
+
- Used in `_monitor_evacuation()` as the hard per-instance timeout
2721
+
- The Nova migrations query window is automatically derived from this value to ensure in-progress migrations remain visible
2722
+
- Also governs the thread-pool future timeout (adds 60s padding) and host-processing staleness tracking
2723
+
2724
+
**Example**:
2725
+
```yaml
2726
+
config:
2727
+
EVACUATION_TIMEOUT: 900
2728
+
```
2729
+
2730
+
---
2731
+
2732
+
#### EVACUATION_STAGGER
2733
+
**Type**: Integer (seconds)
2734
+
**Default**: `0`
2735
+
**Range**: 0-60
2736
+
2737
+
**Description**: Seconds of delay between each VM's evacuation start within a concurrent evacuation phase. VM 0 starts immediately, VM 1 waits `1 * stagger` seconds, VM 2 waits `2 * stagger` seconds, and so on. Default 0 starts all VMs immediately (existing behavior).
2738
+
2739
+
**Usage**:
2740
+
- Applied in `_run_concurrent()`: submissions to the thread pool are staggered with `_interruptible_sleep` between each `executor.submit()` call, respecting shutdown signals
2741
+
- Applies per-phase: in orchestrated mode, each phase's VMs are staggered independently starting from 0
2742
+
- Does not affect the overall `EVACUATION_TIMEOUT` — the timeout starts when the VM's evacuation begins, not when it is queued
2743
+
2744
+
**Motivation**: When many VMs are evacuated concurrently from a single failed host, the burst of parallel API calls (Nova evacuate, Cinder volume attach/detach, Neutron port binding) can saturate the control plane, causing timeouts and failed evacuations. Staggering the submissions spreads the load over time.
2745
+
2746
+
**Example**:
2747
+
```yaml
2748
+
config:
2749
+
SMART_EVACUATION: true
2750
+
WORKERS: 8
2751
+
EVACUATION_STAGGER: 2 # 2s between each VM -- 30 VMs spread over 60s
2752
+
```
2753
+
2754
+
---
2755
+
2680
2756
#### RESERVED_HOSTS
2681
2757
**Type**: Boolean
2682
2758
**Default**: `false`
@@ -2685,7 +2761,7 @@ config:
2685
2761
**Description**: Enable automatic management of reserved compute hosts.
2686
2762
2687
2763
**Usage**:
2688
-
- Controls whether `_manage_reserved_hosts()` is called during evacuation
2764
+
- Controls whether `_manage_reserved_hosts()` is called during recovery (before evacuation)
2689
2765
- Works with `FORCE_RESERVED_HOST_EVACUATION` to determine evacuation target
2690
2766
2691
2767
**Behavior**:
@@ -2828,15 +2904,15 @@ openstack flavor set --property ha-enabled=true m1.small
2828
2904
**Default**: `true`
2829
2905
**Range**: N/A
2830
2906
2831
-
**Description**: Enable aggregate-based evacuability filtering. When enabled, only servers on hosts in aggregates with the evacuable tag are evacuated.
2907
+
**Description**: Enable aggregate-based host filtering. When enabled, only hosts in aggregates with the evacuable tag are processed for fencing and evacuation.
2832
2908
2833
2909
**Usage**:
2834
2910
- Controls whether aggregate tags are checked in `is_aggregate_evacuable()`
2835
2911
- Also controls reserved host matching strategy (aggregate vs zone)
2836
2912
2837
2913
**Behavior**:
2838
2914
- When `true`:
2839
-
- Only evacuate servers if host is in an aggregate with `{EVACUABLE_TAG}: true` metadata
2915
+
- Only process hosts in aggregates with `{EVACUABLE_TAG}: true` metadata
2840
2916
- Reserved hosts matched by shared aggregate
2841
2917
-`THRESHOLD` percentage calculated against active evacuable hosts only
2842
2918
- When `false`:
@@ -3157,7 +3233,7 @@ config:
3157
3233
#### MAX_HOSTS_PER_CYCLE
3158
3234
**Type**: Integer
3159
3235
**Default**: `10`
3160
-
**Range**: `1-100`
3236
+
**Range**: `1-200`
3161
3237
3162
3238
**Description**: Maximum number of hosts that can be fenced in a single poll cycle. If more hosts are detected as down, the first `MAX_HOSTS_PER_CYCLE` are fenced and the rest are deferred to the next cycle. This bounds the blast radius of a single poll cycle -- a Nova API anomaly (conductor crash, stale data, Keystone token issue) cannot trigger mass fencing beyond this cap.
3163
3239
@@ -3181,18 +3257,18 @@ config:
3181
3257
**Default**: `false`
3182
3258
**Range**: N/A
3183
3259
3184
-
**Description**: Disable evacuation processing.
3260
+
**Description**: Disable fencing and evacuation processing.
3185
3261
3186
3262
**Usage**:
3187
3263
- Checked at the beginning of evacuation processing
3188
-
- When `true`: Log "Service disabled" and skip all evacuation logic
3264
+
- When `true`: Log "Service disabled" and skip fencing and evacuation
3189
3265
- When `false`: Normal operation
3190
3266
3191
3267
**Behavior**:
3192
3268
- Service runs and polls Nova API
3193
3269
- Service categorization occurs
3194
3270
- No fencing, disabling, or evacuation actions executed
3195
-
- Health checks continue to update
3271
+
- Health checks and host re-enabling continue to run
0 commit comments