Skip to content

Commit cd52b07

Browse files
committed
Move provision to orchestrator domain and rename provision references to orchestration
- Move src/playbooks/provision/ to src/orchestrator/ (independent domain) - Move src/playbooks/discovery/ to src/discovery/ (independent domain) - Rename provision_validations role to orchestrator_validations - Rename provision.yml to orchestrator.yml - Rename provision_* variables to orchestration_* across orchestrator roles - Rename provision_completion.yml to orchestration_completion.yml - Rename provision_mapping_nodes.yml to orchestration_mapping_nodes.yml - Update user-facing messages to reference orchestrator.yml - Add orchestrator_config.json schema and validation - Fix admin_nic_ip variable scoping in deploy_openchami role - Add retry logic for SMD group data query (3 retries, 15s delay) - Add deploy_openchami role (moved from prepare_oim) - Add ARCHITECTURE_REFACTOR.md documenting the refactoring
1 parent b9fb7d5 commit cd52b07

313 files changed

Lines changed: 3999 additions & 495 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ARCHITECTURE_REFACTOR.md

Lines changed: 620 additions & 0 deletions
Large diffs are not rendered by default.

src/common/library/module_utils/input_validation/common_utils/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"network_spec": "network_spec.yml",
5353
"omnia_config": "omnia_config.yml",
5454
"provision_config": "provision_config.yml",
55+
"orchestrator_config": "orchestrator_config.yml",
5556
"security_config": "security_config.yml",
5657
"software_config": "software_config.json",
5758
"storage_config": "storage_config.yml",
@@ -81,6 +82,10 @@
8182
files["software_config"],
8283
# files["high_availability_config"]
8384
],
85+
"orchestrator": [
86+
files["orchestrator_config"],
87+
files["network_spec"],
88+
],
8489
"security": [
8590
files["security_config"]
8691
],

src/common/library/module_utils/input_validation/common_utils/logical_validation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def validate_input_logic(
5858
# Based on the file_name, run validation function
5959
validation_functions = {
6060
"provision_config.yml": provision_validation.validate_provision_config,
61+
"orchestrator_config.yml": provision_validation.validate_orchestrator_config,
6162
"software_config.json": common_validation.validate_software_config,
6263
"network_spec.yml": provision_validation.validate_network_spec,
6364
"omnia_config.yml": common_validation.validate_omnia_config,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"properties": {
5+
"pxe_mapping_file_path": {
6+
"type": "string",
7+
"description": "Path to the PXE mapping file."
8+
},
9+
"language": {
10+
"type": "string",
11+
"description": "Language setting.",
12+
"default": "en_US.UTF-8"
13+
},
14+
"default_lease_time": {
15+
"type": ["string", "integer"],
16+
"description": "Default lease time for DHCP.",
17+
"default": 86400
18+
},
19+
"dns_enabled": {
20+
"type": "boolean",
21+
"description": "Enable DNS-based hostname resolution via coresmd.",
22+
"default": false
23+
},
24+
"kernel_version_override": {
25+
"type": "string",
26+
"description": "Optional kernel version to pin for boot image selection. Leave empty to auto-select latest.",
27+
"pattern": "^(|[0-9]+\\.[0-9]+\\.[0-9]+-.+)$",
28+
"default": ""
29+
},
30+
"additional_cloud_init_config_file": {
31+
"type": "string",
32+
"description": "Path to additional cloud-init configuration file for stateless node provisioning. Leave empty to disable.",
33+
"default": ""
34+
},
35+
"s3_storage_provider": {
36+
"type": "string",
37+
"description": "S3 storage provider: minio, powerscale, or external.",
38+
"enum": ["minio", "powerscale", "external"],
39+
"default": "minio"
40+
},
41+
"s3_endpoint": {
42+
"type": "string",
43+
"description": "S3-compatible endpoint URL. Required for powerscale/external providers.",
44+
"default": ""
45+
},
46+
"s3_access_key": {
47+
"type": "string",
48+
"description": "S3 access key.",
49+
"default": ""
50+
},
51+
"s3_secret_key": {
52+
"type": "string",
53+
"description": "S3 secret key.",
54+
"default": ""
55+
},
56+
"s3_boot_images_bucket": {
57+
"type": "string",
58+
"description": "S3 bucket name for boot images.",
59+
"default": "boot-images"
60+
},
61+
"s3_verify_ssl": {
62+
"type": "boolean",
63+
"description": "Whether to verify SSL certificates for S3 connections.",
64+
"default": false
65+
}
66+
},
67+
"required": [
68+
"pxe_mapping_file_path",
69+
"language",
70+
"default_lease_time"
71+
]
72+
}

src/common/library/module_utils/input_validation/validation_flows/provision_validation.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,128 @@ def validate_provision_config(
14511451

14521452
return errors
14531453

1454+
1455+
def validate_orchestrator_config(
1456+
input_file_path, data, logger, module, omnia_base_dir, module_utils_base, project_name
1457+
):
1458+
"""
1459+
Validates the orchestrator configuration.
1460+
Subset of validate_provision_config — skips software_config and build_stream
1461+
validation since the orchestrator does not own those files.
1462+
"""
1463+
errors = []
1464+
1465+
# Validate language setting
1466+
language = data.get("language", "")
1467+
if not language:
1468+
errors.append(
1469+
create_error_msg("language", input_file_path, en_us_validation_msg.LANGUAGE_EMPTY_MSG)
1470+
)
1471+
elif "en_US.UTF-8" not in language:
1472+
errors.append(
1473+
create_error_msg("language", input_file_path, en_us_validation_msg.LANGUAGE_FAIL_MSG)
1474+
)
1475+
1476+
pxe_mapping_file_path = data.get("pxe_mapping_file_path", "")
1477+
if pxe_mapping_file_path and validation_utils.verify_path(pxe_mapping_file_path):
1478+
try:
1479+
validate_mapping_file_entries(pxe_mapping_file_path)
1480+
validate_functional_groups_in_mapping_file(pxe_mapping_file_path)
1481+
validate_duplicate_service_tags_in_mapping_file(pxe_mapping_file_path)
1482+
validate_duplicate_hostnames_in_mapping_file(pxe_mapping_file_path)
1483+
validate_duplicate_admin_ips_in_mapping_file(pxe_mapping_file_path)
1484+
validate_duplicate_ib_ips_in_mapping_file(pxe_mapping_file_path)
1485+
validate_ib_nic_name_format_in_mapping_file(pxe_mapping_file_path)
1486+
validate_group_parent_service_tag_consistency_in_mapping_file(pxe_mapping_file_path)
1487+
validate_functional_groups_separation(pxe_mapping_file_path)
1488+
validate_parent_service_tag_hierarchy(pxe_mapping_file_path)
1489+
validate_slurm_login_compiler_prefix(pxe_mapping_file_path)
1490+
validate_aarch64_local_path_compatibility(pxe_mapping_file_path)
1491+
dns_enabled = data.get("dns_enabled", False)
1492+
if isinstance(dns_enabled, str):
1493+
dns_enabled = dns_enabled.lower() in ("true", "yes", "1")
1494+
validate_hostname_nid_format_when_dns_enabled(pxe_mapping_file_path, bool(dns_enabled))
1495+
1496+
# Validate ADMIN_IPs against network_spec.yml subnets
1497+
network_spec_path = create_file_path(input_file_path, file_names["network_spec"])
1498+
if os.path.isfile(network_spec_path):
1499+
try:
1500+
with open(network_spec_path, "r", encoding="utf-8") as f:
1501+
network_spec_json = yaml.safe_load(f)
1502+
1503+
admin_netmaskbits = None
1504+
oim_admin_ip = None
1505+
additional_subnets = []
1506+
1507+
for network in (network_spec_json or {}).get("Networks", []):
1508+
if "admin_network" in network and isinstance(network["admin_network"], dict):
1509+
admin_net = network["admin_network"]
1510+
admin_netmaskbits = admin_net.get("netmask_bits")
1511+
oim_admin_ip = admin_net.get("primary_oim_admin_ip")
1512+
additional_subnets = admin_net.get("additional_subnets") or []
1513+
break
1514+
1515+
if admin_netmaskbits and oim_admin_ip:
1516+
validate_pxe_admin_ips_subnet_consistency(
1517+
errors, pxe_mapping_file_path,
1518+
oim_admin_ip, admin_netmaskbits,
1519+
additional_subnets
1520+
)
1521+
except (yaml.YAMLError, IOError) as e:
1522+
errors.append(
1523+
create_error_msg(
1524+
"network_spec.yml",
1525+
network_spec_path,
1526+
f"Failed to load or parse network_spec.yml: {str(e)}"
1527+
)
1528+
)
1529+
except ValueError as e:
1530+
errors.append(
1531+
create_error_msg(
1532+
"pxe_mapping_file_path",
1533+
pxe_mapping_file_path,
1534+
str(e),
1535+
)
1536+
)
1537+
else:
1538+
errors.append(
1539+
create_error_msg(
1540+
"pxe_mapping_file_path",
1541+
pxe_mapping_file_path,
1542+
en_us_validation_msg.PXE_MAPPING_FILE_PATH_FAIL_MSG,
1543+
)
1544+
)
1545+
1546+
default_lease_time = data["default_lease_time"]
1547+
if not validation_utils.validate_default_lease_time(default_lease_time):
1548+
errors.append(
1549+
create_error_msg(
1550+
"default_lease_time",
1551+
default_lease_time,
1552+
en_us_validation_msg.DEFAULT_LEASE_TIME_FAIL_MSG,
1553+
)
1554+
)
1555+
1556+
kernel_version_override = data.get("kernel_version_override", "")
1557+
if kernel_version_override:
1558+
if not re.match(r"^[0-9]+\.[0-9]+\.[0-9]+-.+$", kernel_version_override):
1559+
errors.append(
1560+
create_error_msg(
1561+
"kernel_version_override",
1562+
kernel_version_override,
1563+
en_us_validation_msg.KERNEL_VERSION_OVERRIDE_FAIL_MSG,
1564+
)
1565+
)
1566+
1567+
# Validate additional cloud-init config file
1568+
aci_path = data.get("additional_cloud_init_config_file", "")
1569+
if aci_path:
1570+
aci_errors = validate_additional_cloud_init_config(aci_path, pxe_mapping_file_path)
1571+
errors.extend(aci_errors)
1572+
1573+
return errors
1574+
1575+
14541576
def validate_network_spec(
14551577
input_file_path, data, logger, module, omnia_base_dir, module_utils_base, project_name
14561578
):

src/common/library/modules/validate_input.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def createlogger(project_name, tag_name=None):
115115
log_filename = f"validation_omnia_{project_name}.log"
116116

117117
log_file_path = os.path.join(config.INPUT_VALIDATOR_LOG_PATH, log_filename)
118+
os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
118119
logging.basicConfig(
119120
filename=log_file_path,
120121
format="%(asctime)s %(message)s",

src/discovery/CONTRACTS.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Discovery Domain – Input/Output Contracts
2+
3+
## Overview
4+
5+
The Discovery domain is responsible for discovering hardware (servers) via
6+
management platforms (e.g., Dell OME) and producing a PXE mapping file that
7+
serves as the primary data contract between Discovery and the Orchestrator.
8+
9+
## Inputs
10+
11+
All inputs are read from:
12+
13+
```
14+
/opt/omnia/input/<project_name>/discovery/
15+
```
16+
17+
### discovery_config.yml
18+
19+
| Parameter | Type | Required | Description |
20+
|-----------|------|----------|-------------|
21+
| `enable_bmc_discovery` | bool | Yes | Enable BMC discovery via OME |
22+
| `ome_ip` | string | Yes (when OME) | IP address of Dell OME instance |
23+
24+
### network_spec.yml
25+
26+
| Parameter | Type | Required | Description |
27+
|-----------|------|----------|-------------|
28+
| `Networks[].admin_network.subnet` | string | Yes | Admin network subnet for IP derivation |
29+
| `Networks[].ib_network.subnet` | string | No | InfiniBand subnet for IB IP derivation |
30+
31+
### Credentials (from shared credential utility)
32+
33+
| Parameter | Source | Description |
34+
|-----------|--------|-------------|
35+
| `ome_username` | omnia_config_credentials.yml | OME login username |
36+
| `ome_password` | omnia_config_credentials.yml | OME login password (vault-encrypted) |
37+
38+
## Outputs
39+
40+
All outputs are written to:
41+
42+
```
43+
/opt/omnia/output/<project_name>/discovery/
44+
```
45+
46+
### bmc_pxe_mapping_file.csv (Primary Contract)
47+
48+
This is the **primary output** consumed by the Orchestrator domain.
49+
50+
| Column | Type | Description |
51+
|--------|------|-------------|
52+
| `FUNCTIONAL_GROUP_NAME` | string | Node functional group (e.g., `slurm_node_aarch64`) |
53+
| `GROUP_NAME` | string | Scalable Unit / group identifier |
54+
| `SERVICE_TAG` | string | Dell server service tag |
55+
| `PARENT_SERVICE_TAG` | string | Parent node service tag (for slurm child nodes) |
56+
| `HOSTNAME` | string | Generated hostname (e.g., `nid00001`) |
57+
| `ADMIN_MAC` | string | Admin NIC MAC address |
58+
| `ADMIN_IP` | string | Admin IP (derived from admin_subnet + BMC IP) |
59+
| `BMC_MAC` | string | BMC/iDRAC MAC address |
60+
| `BMC_IP` | string | BMC/iDRAC IP address |
61+
| `IB_NIC_NAME` | string | InfiniBand NIC FQDD (if present) |
62+
| `IB_IP` | string | InfiniBand IP (derived from ib_subnet + BMC IP) |
63+
64+
### bmc_discovery_report.csv (Informational)
65+
66+
NIC link status report for operator review. Not consumed programmatically.
67+
68+
## Data Flow
69+
70+
```
71+
Discovery Input Discovery Output Orchestrator Input
72+
───────────────── ───────────────── ──────────────────
73+
discovery_config.yml ─┐
74+
├─► OME ─► bmc_pxe_mapping_file.csv ──copy──► pxe_mapping_file.csv
75+
network_spec.yml ─────┘ bmc_discovery_report.csv
76+
```
77+
78+
The mapping file must be **manually reviewed and copied** to the orchestrator
79+
input directory before running orchestrator.yml. This deliberate handoff point
80+
ensures operators can review/edit node assignments before provisioning.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[defaults]
2-
log_path = /opt/omnia/log/core/playbooks/provision.log
2+
log_path = /opt/omnia/log/core/discovery/discovery.log
33
remote_tmp = /opt/omnia/tmp/.ansible/tmp/
44
host_key_checking = false
55
forks = 5
@@ -9,9 +9,9 @@ interpreter_python = /usr/bin/python3
99
deprecation_warnings = false
1010
show_task_path_on_failure = false
1111
stdout_callback = omnia_default
12-
callback_plugins = ../../common/callback_plugins
13-
library = library:../../common/library/modules
14-
module_utils = ../../common/library/module_utils
12+
callback_plugins = ../common/callback_plugins
13+
library = roles/ome_discovery/library:../common/library/modules
14+
module_utils = ../common/library/module_utils
1515

1616
[persistent_connection]
1717
command_timeout = 180

0 commit comments

Comments
 (0)