Skip to content
Merged
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
18 changes: 7 additions & 11 deletions ansible/cluster_info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
validate_certs: "{{ ontap_validate_certs }}"
use_rest: always
gather_subset:
- cluster/node_info
- cluster/nodes
fields:
- "version"
register: cluster_result
Expand All @@ -29,12 +29,8 @@
- name: Display cluster version
ansible.builtin.debug:
msg: >-
Cluster: {{ cluster_result.ontap_info['cluster/node_info']
| dict2items | first
| json_query('value.cluster_name') | default('unknown') }}
— ONTAP {{ cluster_result.ontap_info['cluster/node_info']
| dict2items | first
| json_query('value.version') | default('unknown') }}
Cluster node: {{ cluster_result.ontap_info['cluster/nodes']['records'][0].name | default('unknown') }}
— ONTAP {{ cluster_result.ontap_info['cluster/nodes']['records'][0].version.full | default('unknown') }}

# -- Step 2: List nodes with serial numbers -------------------------
- name: Get node details
Expand All @@ -46,7 +42,7 @@
validate_certs: "{{ ontap_validate_certs }}"
use_rest: always
gather_subset:
- cluster/node_info
- cluster/nodes
fields:
- "name"
- "serial_number"
Expand All @@ -55,7 +51,7 @@

- name: Display node list
ansible.builtin.debug:
msg: "Node: {{ item.value.name }} — serial: {{ item.value.serial_number | default('N/A') }}"
loop: "{{ nodes_result.ontap_info['cluster/node_info'] | dict2items }}"
msg: "Node: {{ item.name }} — serial: {{ item.serial_number | default('N/A') }}"
loop: "{{ nodes_result.ontap_info['cluster/nodes']['records'] }}"
loop_control:
label: "{{ item.value.name }}"
label: "{{ item.name }}"
222 changes: 222 additions & 0 deletions ansible/cluster_setup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
# cluster_setup_basic.yml — Basic ONTAP cluster setup via REST API.
#
# Mirrors the cluster_setup_basic workflow:
# 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
#
# Override variables:
# ansible-playbook -i inventory/hosts.yml cluster_setup_basic.yml \
# -e cluster_name=my_cluster -e cluster_pass=MySecret123 \
# -e partner_mgmt_ip=10.10.10.2
#
- name: "Basic ONTAP Cluster Setup"
hosts: ontap
gather_facts: false
connection: local
module_defaults:
group/netapp.ontap.netapp_ontap:
hostname: "{{ ontap_hostname }}"
username: "{{ ontap_username }}"
password: "{{ ontap_password }}"
https: "{{ ontap_https }}"
validate_certs: "{{ ontap_validate_certs }}"
use_rest: always

vars:
# -- Cluster configuration ------------------------------------------
cluster_name: ""
cluster_pass: ""
cluster_mgmt_ip: ""
cluster_netmask: ""
cluster_gateway: ""
partner_mgmt_ip: ""

# -- 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"

tasks:
# -- Pre-flight checks ----------------------------------------------
- name: "Validate required variables"
ansible.builtin.assert:
that:
- cluster_name | length > 0
- cluster_pass | length > 0
- cluster_mgmt_ip | length > 0
- cluster_netmask | length > 0
- cluster_gateway | length > 0
- partner_mgmt_ip | length > 0
fail_msg: >-
Required variables: cluster_name, cluster_pass, cluster_mgmt_ip,
cluster_netmask, cluster_gateway, partner_mgmt_ip
no_log: false

# -- Step 1: Discover available nodes (reusable pre-check) ----------
- name: "Discover available nodes (retry 3x / 30s)"
ansible.builtin.uri:
url: "{{ api_url_base }}/cluster/nodes?fields={{ node_query_fields }}&membership=available&return_timeout=120"
method: GET
url_username: "{{ ontap_username }}"
url_password: "{{ ontap_password }}"
force_basic_auth: true
validate_certs: "{{ ontap_validate_certs }}"
headers:
Accept: "application/json"
status_code: 200
timeout: 150
register: discover_nodes
retries: 3
delay: 30
until: discover_nodes is succeeded and (discover_nodes.json.num_records | default(0)) > 0
no_log: false

- name: "Show discovered nodes"
ansible.builtin.debug:
msg: "Discovered {{ discover_nodes.json.num_records }} available node(s)"
no_log: false

# -- Step 2: Discover local node (has management_interfaces) --------
- name: "Discover local node (retry 3x / 30s)"
ansible.builtin.uri:
url: "{{ api_url_base }}/cluster/nodes?fields={{ node_query_fields }}&membership=available&management_interfaces=!null&return_timeout=120"
method: GET
url_username: "{{ ontap_username }}"
url_password: "{{ ontap_password }}"
force_basic_auth: true
validate_certs: "{{ ontap_validate_certs }}"
headers:
Accept: "application/json"
status_code: 200
timeout: 150
register: discover_local
retries: 3
delay: 30
until: discover_local is succeeded and (discover_local.json.num_records | default(0)) > 0
no_log: false

- name: "Show discovered local node"
ansible.builtin.debug:
msg: >-
Local node: {{ discover_local.json.records[0].name }}
(uuid: {{ discover_local.json.records[0].uuid }},
cluster_if: {{ discover_local.json.records[0].cluster_interfaces[0].ip.address }})
no_log: false

# -- Step 3: Discover partner node (exclude local uuid) -------------
- name: "Discover partner node (retry 3x / 30s)"
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"

Check failure on line 116 in ansible/cluster_setup.yml

View workflow job for this annotation

GitHub Actions / Ansible — syntax & lint

yaml[line-length]

Line too long (166 > 160 characters)
method: GET
url_username: "{{ ontap_username }}"
url_password: "{{ ontap_password }}"
force_basic_auth: true
validate_certs: "{{ ontap_validate_certs }}"
headers:
Accept: "application/json"
status_code: 200
timeout: 150
register: discover_partner
retries: 3
delay: 30
until: discover_partner is succeeded and (discover_partner.json.num_records | default(0)) > 0
no_log: false

- name: "Show discovered partner node"
ansible.builtin.debug:
msg: >-
Partner node: {{ discover_partner.json.records[0].name }}
(uuid: {{ discover_partner.json.records[0].uuid }},
cluster_if: {{ discover_partner.json.records[0].cluster_interfaces[0].ip.address }})
no_log: false

# -- Step 4: Create cluster (POST /api/cluster) ---------------------
- name: "Create cluster '{{ cluster_name }}'"
ansible.builtin.uri:
url: "{{ api_url_base }}/cluster?keep_precluster_config=true"
method: POST
url_username: "{{ ontap_username }}"
url_password: "{{ ontap_password }}"
force_basic_auth: true
validate_certs: "{{ ontap_validate_certs }}"
headers:
Accept: "application/json"
Content-Type: "application/json"
body_format: json
body:
name: "{{ cluster_name }}"
password: "{{ cluster_pass }}"
management_interface:
ip:
address: "{{ cluster_mgmt_ip }}"
netmask: "{{ cluster_netmask }}"
gateway: "{{ cluster_gateway }}"
nodes:
- name: "{{ cluster_name }}-01"
management_interface:
ip:
address: "{{ ontap_hostname }}"
cluster_interface:
ip:
address: "{{ discover_local.json.records[0].cluster_interfaces[0].ip.address }}"
- name: "{{ cluster_name }}-02"
management_interface:
ip:
address: "{{ partner_mgmt_ip }}"
cluster_interface:
ip:
address: "{{ discover_partner.json.records[0].cluster_interfaces[0].ip.address }}"
name_servers: {}
ntp_servers: {}
dns_domains: {}
configuration_backup: {}
status_code: 202
timeout: 60
register: create_cluster
no_log: false

- name: "Show create-cluster job UUID"
ansible.builtin.debug:
msg: "Job UUID: {{ create_cluster.json.job.uuid }}"
no_log: false

# -- Step 5: Poll job until complete --------------------------------
- name: "Poll cluster-create job until complete"
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"

Check failure on line 193 in ansible/cluster_setup.yml

View workflow job for this annotation

GitHub Actions / Ansible — syntax & lint

yaml[line-length]

Line too long (190 > 160 characters)
method: GET
url_username: "{{ ontap_username }}"
url_password: "{{ cluster_pass }}"
force_basic_auth: true
validate_certs: "{{ ontap_validate_certs }}"
headers:
Accept: "application/json"
status_code: 200
timeout: 150
register: job_status
retries: 30
delay: 20
until: job_status.json.state in ["success", "failure"]
no_log: false

- name: "Fail if cluster-create job failed"
ansible.builtin.fail:
msg: "Cluster create job failed: {{ job_status.json.message | default(job_status.json.error | default('unknown error')) }}"
when: job_status.json.state == "failure"
no_log: false

# -- Summary --------------------------------------------------------
- name: "Print setup summary"
ansible.builtin.debug:
msg: >-
Cluster '{{ cluster_name }}' created successfully.
Cluster mgmt: {{ cluster_mgmt_ip }}/{{ cluster_netmask }} gw {{ cluster_gateway }}.
Node 1 (local): {{ ontap_hostname }} cluster-if {{ discover_local.json.records[0].cluster_interfaces[0].ip.address }}.
Node 2 (partner): {{ partner_mgmt_ip }} cluster-if {{ discover_partner.json.records[0].cluster_interfaces[0].ip.address }}.
Loading