Skip to content

Commit 340f812

Browse files
Merge pull request #620 from openshift-cherrypick-robot/cherry-pick-619-to-18.0-fr6
[18.0-fr6] InstanceHA robustness and tunability improvements
2 parents 27d3743 + 838957e commit 340f812

11 files changed

Lines changed: 750 additions & 129 deletions

docs/instanceha_architecture.md

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ _config_map: Dict[str, ConfigItem] = {
110110
'DELTA': ConfigItem('int', 30, 10, 300),
111111
'POLL': ConfigItem('int', 45, 15, 600),
112112
'THRESHOLD': ConfigItem('int', 50, 0, 100),
113-
'WORKERS': ConfigItem('int', 4, 1, 50),
113+
'WORKERS': ConfigItem('int', 4, 1, 100),
114114
'DELAY': ConfigItem('int', 0, 0, 300),
115115
'LOGLEVEL': ConfigItem('str', 'INFO'),
116116
'SMART_EVACUATION': ConfigItem('bool', False),
@@ -131,10 +131,12 @@ _config_map: Dict[str, ConfigItem] = {
131131
'HASH_INTERVAL': ConfigItem('int', 60, 30, 300),
132132
'ORCHESTRATED_RESTART': ConfigItem('bool', False),
133133
'SKIP_SERVERS_WITH_NAME': ConfigItem('list', []),
134+
'EVACUATION_MAX_THREADS': ConfigItem('int', MAX_TOTAL_EVACUATION_THREADS, 1, 512), # MAX_TOTAL_EVACUATION_THREADS = 32
134135
'EVACUATION_RETRIES': ConfigItem('int', DEFAULT_EVACUATION_RETRIES, 1, 20), # DEFAULT_EVACUATION_RETRIES = 5
136+
'EVACUATION_STAGGER': ConfigItem('int', 0, 0, 60),
135137
'HEARTBEAT_CLIFF_THRESHOLD': ConfigItem('int', 50, 10, 100),
136138
'HEARTBEAT_CLIFF_MAX_CYCLES': ConfigItem('int', 3, 1, 20),
137-
'MAX_HOSTS_PER_CYCLE': ConfigItem('int', 10, 1, 100),
139+
'MAX_HOSTS_PER_CYCLE': ConfigItem('int', 10, 1, 200),
138140
'K8S_API_CHECK_INTERVAL': ConfigItem('int', 15, 5, 120),
139141
}
140142
```
@@ -696,7 +698,7 @@ Poll cycle starts
696698
├─ Gate 4: All-services-stale circuit breaker
697699
│ Question: "Is the data source itself broken?"
698700
│ 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
700702
│ infrastructure anomaly, not a genuine failure of every server.
701703
│ Runs after heartbeat filtering so heartbeat data can disambiguate partial
702704
│ 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
742744
|------|-------------------|-----------|
743745
| 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 |
744746
| 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 |
746748
| Heartbeat (grace period) | Fail-open (allow fencing) | No heartbeat history in first `HEARTBEAT_TIMEOUT` seconds -- cannot distinguish "no heartbeat" from "never received one" |
747749
| Heartbeat (steady state) | Fail-open (allow fencing) | No heartbeat = host is down = proceed with fencing |
748750
| Cliff detection | Fail-closed (block fencing), expires after `HEARTBEAT_CLIFF_MAX_CYCLES` | Mass heartbeat loss is treated as observer failure; expiry prevents permanent paralysis |
@@ -1005,7 +1007,7 @@ def _server_evacuate_future(connection, server, target_host=None) -> bool:
10051007
- Separate counters for migration errors (`EVACUATION_RETRIES`, default 5, configurable 1-20) and API errors (`MAX_API_RETRIES=10`)
10061008
- API error counter resets on successful API call
10071009
- On migration failure: resets server state to `error` and re-issues `_server_evacuate` (scheduler picks new target)
1008-
- Hard timeout: `MAX_EVACUATION_TIMEOUT_SECONDS` (300s) via `time.monotonic()`
1010+
- Hard timeout: `EVACUATION_TIMEOUT` (default 300s, configurable 60-3600) via `time.monotonic()`
10091011
- Per-instance K8s events emitted: `InstanceEvacuationStarted`, `InstanceEvacuationSucceeded`, `InstanceEvacuationFailed`
10101012

10111013
**Behavior**:
@@ -1015,7 +1017,7 @@ def _server_evacuate_future(connection, server, target_host=None) -> bool:
10151017
- Target host retry: if evacuation to a specific host fails with 409/500, retries without target (scheduler picks)
10161018
- Migration failures trigger automatic re-issue (reset state + new evacuate call)
10171019
- 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)
10191021
- Skips redundant state restore when Nova already preserved the original state after evacuation
10201022
- Restores original VM state after successful evacuation only when needed (`SHUTOFF` -> stop, `ERROR` -> reset_state)
10211023
- Unlocks the server in `finally` block (even on failure)
@@ -2303,7 +2305,7 @@ config:
23032305

23042306
### Why the Defaults Are Conservative
23052307

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:
23072309
- False positives (unnecessary fencing) are more dangerous than false negatives (delayed evacuation)
23082310
- On larger clusters, losing >50% of nodes simultaneously almost always indicates a network partition, not real failures
23092311
- Small clusters are the exception, not the rule, in production OpenStack deployments
@@ -2444,16 +2446,16 @@ config:
24442446
#### WORKERS
24452447
**Type**: Integer
24462448
**Default**: `4`
2447-
**Range**: `1-50`
2449+
**Range**: `1-100`
24482450

24492451
**Description**: Thread pool size for concurrent processing. Controls parallelism at two levels: host-level processing and per-host server evacuation.
24502452

24512453
**Usage**:
24522454
- **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.
24542456

24552457
**Testing**:
2456-
- Unit tests verify range validation (1-50)
2458+
- Unit tests verify range validation (1-100)
24572459
- Functional tests use custom values (6, 8) to test concurrent evacuations
24582460
- Performance tests measure evacuation throughput with different worker counts
24592461
- Test files: `test_instanceha.py:191`, functional tests
@@ -2464,7 +2466,7 @@ config:
24642466
WORKERS: 8 # Process 8 hosts concurrently; each host gets up to 4 per-host threads (32/8)
24652467
```
24662468

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).
24682470

24692471
---
24702472

@@ -2576,6 +2578,7 @@ config:
25762578
**Notes**:
25772579
- Smart mode increases API load (polling) and memory usage (thread pool)
25782580
- 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
25792582

25802583
---
25812584

@@ -2657,6 +2660,35 @@ This would skip servers named `test-vm-1`, `dev-instance-02`, etc.
26572660

26582661
---
26592662

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
2674+
2675+
**Per-host concurrency examples**:
2676+
2677+
| WORKERS | EVACUATION_MAX_THREADS | inner_workers (per host) |
2678+
|---------|----------------------|--------------------------|
2679+
| 4 | 32 (default) | 8 |
2680+
| 10 | 32 (default) | 3 |
2681+
| 10 | 100 | 10 |
2682+
| 4 | 64 | 16 |
2683+
2684+
**Example**:
2685+
```yaml
2686+
config:
2687+
EVACUATION_MAX_THREADS: 100 # With WORKERS=10, gives 10 concurrent evacuations per host
2688+
```
2689+
2690+
---
2691+
26602692
#### EVACUATION_RETRIES
26612693
**Type**: Integer
26622694
**Default**: `5`
@@ -2667,7 +2699,7 @@ This would skip servers named `test-vm-1`, `dev-instance-02`, etc.
26672699
**Usage**:
26682700
- Used in `_monitor_evacuation()` to cap migration-error retries
26692701
- 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`)
26712703

26722704
**Example**:
26732705
```yaml
@@ -2677,6 +2709,50 @@ config:
26772709

26782710
---
26792711

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+
26802756
#### RESERVED_HOSTS
26812757
**Type**: Boolean
26822758
**Default**: `false`
@@ -2685,7 +2761,7 @@ config:
26852761
**Description**: Enable automatic management of reserved compute hosts.
26862762

26872763
**Usage**:
2688-
- Controls whether `_manage_reserved_hosts()` is called during evacuation
2764+
- Controls whether `_manage_reserved_hosts()` is called during recovery (before evacuation)
26892765
- Works with `FORCE_RESERVED_HOST_EVACUATION` to determine evacuation target
26902766

26912767
**Behavior**:
@@ -2828,15 +2904,15 @@ openstack flavor set --property ha-enabled=true m1.small
28282904
**Default**: `true`
28292905
**Range**: N/A
28302906

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.
28322908

28332909
**Usage**:
28342910
- Controls whether aggregate tags are checked in `is_aggregate_evacuable()`
28352911
- Also controls reserved host matching strategy (aggregate vs zone)
28362912

28372913
**Behavior**:
28382914
- 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
28402916
- Reserved hosts matched by shared aggregate
28412917
- `THRESHOLD` percentage calculated against active evacuable hosts only
28422918
- When `false`:
@@ -3157,7 +3233,7 @@ config:
31573233
#### MAX_HOSTS_PER_CYCLE
31583234
**Type**: Integer
31593235
**Default**: `10`
3160-
**Range**: `1-100`
3236+
**Range**: `1-200`
31613237

31623238
**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.
31633239

@@ -3181,18 +3257,18 @@ config:
31813257
**Default**: `false`
31823258
**Range**: N/A
31833259

3184-
**Description**: Disable evacuation processing.
3260+
**Description**: Disable fencing and evacuation processing.
31853261

31863262
**Usage**:
31873263
- 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
31893265
- When `false`: Normal operation
31903266

31913267
**Behavior**:
31923268
- Service runs and polls Nova API
31933269
- Service categorization occurs
31943270
- No fencing, disabling, or evacuation actions executed
3195-
- Health checks continue to update
3271+
- Health checks and host re-enabling continue to run
31963272

31973273
**Testing**:
31983274
- Configuration feature tests verify evacuation skipping
@@ -3220,11 +3296,11 @@ config:
32203296
**Default**: `true`
32213297
**Range**: N/A
32223298

3223-
**Description**: Enable SSL/TLS certificate verification for HTTPS requests.
3299+
**Description**: Enable SSL/TLS certificate verification for Redfish fencing HTTPS requests.
32243300

32253301
**Usage**:
3226-
- Controls `verify` parameter in `requests` library calls
3227-
- Affects Redfish fencing and Kubernetes API (BMH) connections
3302+
- Controls `verify` parameter in `requests` library calls via `get_requests_ssl_config()`
3303+
- Affects Redfish fencing connections only (K8s API uses the service account CA cert independently)
32283304
**Behavior**:
32293305
- **Enabled** (`true`):
32303306
- Use `get_requests_ssl_config()` to determine SSL configuration

0 commit comments

Comments
 (0)