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
2 changes: 1 addition & 1 deletion .github/workflows/pr-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: TruffleHog scan
uses: trufflesecurity/trufflehog@v3.94.3
with:
extra_args: --only-verified --fail
extra_args: --only-verified

yaml-syntax:
name: Validate YAML syntax
Expand Down
25 changes: 16 additions & 9 deletions ansible/cluster_setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
# See the NOTICE file in the repo root for trademark and attribution details.

---
# cluster_setup_basic.yml — Basic storage cluster setup via REST API.
# cluster_setup.yml — Basic storage cluster setup via REST API.
#
# Mirrors the cluster_setup_basic workflow:
# Steps:
# 1. Discover available nodes (retry 3x/30s)
# 2. Discover local node (has management_interfaces, retry 3x/30s)
# 3. Discover partner node (exclude local uuid, retry 3x/30s)
# 4. POST /api/cluster with discovered node info
# 5. Poll job until complete
#
# Usage:
# ansible-playbook -i inventory/hosts.yml cluster_setup_basic.yml
# ansible-playbook -i inventory/hosts.yml cluster_setup.yml
#
# Override variables:
# ansible-playbook -i inventory/hosts.yml cluster_setup_basic.yml \
# -e cluster_name=my_cluster -e cluster_pass=MySecret123 \
# ansible-playbook -i inventory/hosts.yml cluster_setup.yml \
# -e cluster_name=my_cluster -e cluster_pass=<your-password> \
# -e partner_mgmt_ip=10.10.10.2
#
- name: "Basic Storage Cluster Setup"
- name: "Basic Storage Cluster Setup (ONTAP 9 unified)"
hosts: ontap
gather_facts: false
connection: local
Expand All @@ -45,7 +45,7 @@
# -- REST API defaults ----------------------------------------------
api_url_base: "https://{{ ontap_hostname }}/api"
api_auth: "{{ ontap_username }}:{{ ontap_password }}"
node_query_fields: "name,model,state,ha,version,serial_number,membership,cluster_interfaces,management_interfaces,metrocluster,disaggregated,san_optimized"
node_query_fields: "name,model,state,ha,version,serial_number,membership,cluster_interfaces,management_interfaces,metrocluster"

tasks:
# -- Pre-flight checks ----------------------------------------------
Expand Down Expand Up @@ -116,8 +116,12 @@

# -- Step 3: Discover partner node (exclude local uuid) -------------
- name: "Discover partner node (retry 3x / 30s)"
vars:
local_uuid: "{{ discover_local.json.records[0].uuid }}"
partner_url: >-
{{ api_url_base }}/cluster/nodes?fields={{ node_query_fields }}&membership=available&uuid=!{{ local_uuid }}&return_timeout=120
ansible.builtin.uri:
url: "{{ api_url_base }}/cluster/nodes?fields={{ node_query_fields }}&membership=available&uuid=!{{ discover_local.json.records[0].uuid }}&return_timeout=120"
url: "{{ partner_url }}"
method: GET
url_username: "{{ ontap_username }}"
url_password: "{{ ontap_password }}"
Expand Down Expand Up @@ -193,8 +197,11 @@

# -- Step 5: Poll job until complete --------------------------------
- name: "Poll cluster-create job until complete"
vars:
job_fields: "code,description,end_time,error,_links,message,start_time,state,svm,uuid,arguments"
job_url: "{{ api_url_base }}/cluster/jobs/{{ create_cluster.json.job.uuid }}?fields={{ job_fields }}&return_timeout=120"
ansible.builtin.uri:
url: "{{ api_url_base }}/cluster/jobs/{{ create_cluster.json.job.uuid }}?fields=code,description,end_time,error,_links,message,start_time,state,svm,uuid,arguments&return_timeout=120"
url: "{{ job_url }}"
method: GET
url_username: "{{ ontap_username }}"
url_password: "{{ cluster_pass }}"
Expand Down
43 changes: 8 additions & 35 deletions python/cluster_setup_basic.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#!/usr/bin/env python3
# © 2026 NetApp, Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# See the NOTICE file in the repo root for trademark and attribution details.

"""Create a storage cluster from two pre-cluster nodes.

Equivalent to: orchestrio run yaml-workflows/workflows/cluster_setup_basic.yaml
"""Create a storage cluster from two pre-cluster nodes (ONTAP 9 unified).

Steps:
1. discover_nodes — GET /api/cluster/nodes (membership=available, retry 3x/30s)
Expand Down Expand Up @@ -59,23 +56,11 @@
}
# ---------------------------------------------------------------------------

_NODE_FIELDS_SETS = [
# newest (9.19+)
(
"name,model,state,ha,version,serial_number,membership,"
"cluster_interfaces,management_interfaces,metrocluster,disaggregated,san_optimized"
),
# 9.18 without disaggregated
(
"name,model,state,ha,version,serial_number,membership,"
"cluster_interfaces,management_interfaces,metrocluster,san_optimized"
),
# 9.14 and older — minimal safe set
(
"name,model,state,ha,version,serial_number,membership,"
"cluster_interfaces,management_interfaces,metrocluster"
),
]
# ONTAP 9 unified — node discovery fields
_NODE_FIELDS = (
"name,model,state,ha,version,serial_number,membership,"
"cluster_interfaces,management_interfaces,metrocluster"
)


def _env(key: str, required: bool = True) -> str:
Expand All @@ -96,20 +81,8 @@ def _env(key: str, required: bool = True) -> str:


def _get_nodes(client: OntapClient, **kwargs) -> dict:
"""GET /cluster/nodes, trying progressively reduced field sets for older ONTAP versions."""
last_exc: Exception | None = None
for fields in _NODE_FIELDS_SETS:
try:
return client.get("/cluster/nodes", fields=fields, **kwargs)
except Exception as exc:
if "262197" in str(exc):
logger.warning(
"discover: field unsupported on this version, retrying with reduced fields"
)
last_exc = exc
continue
raise
raise last_exc # type: ignore[misc]
"""GET /cluster/nodes with the standard ONTAP 9 unified field set."""
return client.get("/cluster/nodes", fields=_NODE_FIELDS, **kwargs)


def discover_nodes(client: OntapClient, attempts: int = 3, delay: int = 30) -> dict:
Expand Down
Loading