Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
422 changes: 232 additions & 190 deletions python/cifs_provision.py

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions python/cluster_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"""Retrieve ONTAP cluster version and list all nodes with serial numbers.

Steps:
1. GET /cluster ΓÇö retrieve cluster name and ONTAP version
2. GET /cluster/nodes ΓÇö list all nodes with serial numbers
1. GET /cluster - retrieve cluster name and ONTAP version
2. GET /cluster/nodes - list all nodes with serial numbers

Prerequisites::

Expand All @@ -30,25 +30,26 @@


def main() -> None:
"""Retrieve the cluster ONTAP version and list all nodes with serial numbers."""
with OntapClient.from_env() as client:
# Step 1 ΓÇö cluster version
# Step 1: cluster version
cluster = client.get("/cluster", fields="version")
logger.info(
"Cluster: %s ΓÇö ONTAP %s",
"Cluster: %s - ONTAP %s",
cluster.get("name", "unknown"),
cluster.get("version", {}).get("full", "unknown"),
)

# Step 2 ΓÇö node list with serial numbers
# Step 2: node list with serial numbers
nodes_resp = client.get("/cluster/nodes", fields="name,serial_number")
records = nodes_resp.get("records", [])
logger.info("Nodes in cluster: %d", nodes_resp.get("num_records", len(records)))

for node in records:
logger.info(
" %-30s serial: %s",
node.get("name", "ΓÇö"),
node.get("serial_number", "ΓÇö"),
node.get("name", "N/A"),
node.get("serial_number", "N/A"),
)


Expand Down
87 changes: 72 additions & 15 deletions python/cluster_setup_basic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Create an ONTAP cluster from two pre-cluster nodes.

Equivalent to: orchestrio run yaml-workflows/workflows/cluster_setup_basic.yaml

Steps:
1. discover_nodes — GET /api/cluster/nodes (membership=available, retry 3x/30s)
Expand All @@ -12,6 +11,7 @@

Usage::

# env vars directly
export ONTAP_HOST=10.x.x.x # pre-cluster node IP
export ONTAP_USER=admin # usually admin, empty pass on pre-cluster nodes
export ONTAP_PASS=
Expand All @@ -22,14 +22,19 @@
export CLUSTER_GATEWAY=10.x.x.1
export PARTNER_MGMT_IP=10.x.x.y
python cluster_setup_basic.py

# or supply values via a KEY=VALUE env file
python cluster_setup_basic.py --env-file cluster.env
"""

from __future__ import annotations

import argparse
import logging
import os
import sys
import time
from pathlib import Path

from ontap_client import OntapClient

Expand Down Expand Up @@ -75,6 +80,10 @@


def _env(key: str, required: bool = True) -> str:
"""Return the value for *key* from the INPUTS dict, falling back to the environment.

Exits with an error log if *required* is True and the key is unset.
"""
# Prefer value from INPUTS dict; fall back to environment variable.
val = INPUTS.get(key) or os.environ.get(key, "")
if required and not val:
Expand Down Expand Up @@ -157,20 +166,24 @@ def discover_partner(client: OntapClient, local_uuid: str) -> dict:
return result


def create_cluster(client: OntapClient, local: dict, partner: dict) -> dict:
"""Step 4 — POST /api/cluster to create the cluster."""
cluster_name = _env("CLUSTER_NAME")
cluster_pass = _env("CLUSTER_PASS")
cluster_mgmt_ip = _env("CLUSTER_MGMT_IP")
cluster_netmask = _env("CLUSTER_NETMASK")
cluster_gateway = _env("CLUSTER_GATEWAY")
ontap_host = _env("ONTAP_HOST")
partner_mgmt_ip = _env("PARTNER_MGMT_IP")

local_node = local["records"][0]
partner_node = partner["records"][0]

body = {
def _build_cluster_body(
local_node: dict,
partner_node: dict,
cluster_name: str,
cluster_pass: str,
cluster_mgmt_ip: str,
cluster_netmask: str,
cluster_gateway: str,
ontap_host: str,
partner_mgmt_ip: str,
) -> dict:
"""Build and return the POST body for the /cluster endpoint.

The body includes cluster management interface, both node management
interfaces, cluster interfaces, and empty placeholder keys required
by the ONTAP REST API.
"""
return {
"name": cluster_name,
"password": cluster_pass,
"management_interface": {
Expand Down Expand Up @@ -202,6 +215,20 @@ def create_cluster(client: OntapClient, local: dict, partner: dict) -> dict:
"configuration_backup": {},
}


def create_cluster(client: OntapClient, local: dict, partner: dict) -> dict:
"""Step 4 — POST /api/cluster to create the cluster."""
body = _build_cluster_body(
local_node=local["records"][0],
partner_node=partner["records"][0],
cluster_name=_env("CLUSTER_NAME"),
cluster_pass=_env("CLUSTER_PASS"),
cluster_mgmt_ip=_env("CLUSTER_MGMT_IP"),
cluster_netmask=_env("CLUSTER_NETMASK"),
cluster_gateway=_env("CLUSTER_GATEWAY"),
ontap_host=_env("ONTAP_HOST"),
partner_mgmt_ip=_env("PARTNER_MGMT_IP"),
)
result = client.post("/cluster?keep_precluster_config=true", body)
job_uuid = result.get("job", {}).get("uuid")
logger.info("create_cluster — job %s", job_uuid)
Expand Down Expand Up @@ -235,6 +262,7 @@ def track_job(client: OntapClient, job_uuid: str) -> dict:


def main() -> None:
"""Create an ONTAP cluster from two pre-cluster nodes discovered via the REST API."""
host = _env("ONTAP_HOST")
user = _env("ONTAP_USER")
passwd = os.environ.get("ONTAP_PASS", "") # empty on pre-cluster nodes
Expand All @@ -259,7 +287,36 @@ def main() -> None:
)


def _load_env_file(path: str) -> None:
"""Load KEY=VALUE pairs from a .env file into the INPUTS dict."""
for line in Path(path).read_text().splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
INPUTS[key.strip()] = value.strip().strip('"').strip("'")


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Create an ONTAP cluster from two pre-cluster nodes."
)
parser.add_argument(
"--env-file",
metavar="FILE",
help="Path to a .env file with KEY=VALUE pairs (one per build, like -ir in ha_create.exp).",
)
args = parser.parse_args()

if args.env_file:
_load_env_file(args.env_file)

# env vars always win over INPUTS block defaults
for key in list(INPUTS):
val = os.environ.get(key)
if val:
INPUTS[key] = val

try:
main()
except KeyboardInterrupt:
Expand Down
Loading
Loading