One of the few shortcomings that Ansible has, is that everything has to be defined.  Ansible  will not make decisions for you.  However, with a little mix of json and conditional statements, you can still get Ansible to provide the decision-like outcome you are looking for.

In this post I will go over the structure of the return information from the na_ontap_info module, how to reference the sections you want to use, and how to create a conditional statement so that only the information you specifically want will be in the final output.

For this example, we are going to look at a real-life use case:

  • Restoring the most recent snapshot

If you have snapshot naming set to use ordinal instead of time_stamp (Reference: https://kb.netapp.com/Advice_and_Troubleshooting/Data_Storage_Software/ONTAP_OS/Snapshot_naming_enhancements_in_ONTAP_9.0), this is not something you might have to look up, as hourly.0 will be the most current snapshot.

However, if you do use time_stamp, which is the default, or you do custom or random snapshots, it’s not always possible or easy to know the name of the most current snapshot for a given volume.

This example, Restoring the most recent snapshot, will work no matter how you name your snapshots, because we will be using the time that the snapshot was created as our reference.

First let me show you the playbook, then I will break down the sections.

---
- hosts: localhost
  gather_facts: false
  collections:
    - netapp.ontap
  vars:
    time: []
    vserver: ansible_vserver
    volname: testing
    login: &login
      hostname: 172.31.199.151
      username: admin
      password: netapp123
      https: true
      validate_certs: false
  tasks:
  - name: Info
    na_ontap_info:
      state: info
      vserver: “{{ vserver }}”
      gather_subset: snapshot_info
      <<: *login
    register: ontap
  - set_fact:
      time: "{{ time }} + ['{{ ontap.ontap_info.snapshot_info[item].access_time }}' ]"
    with_items: "{{ ontap.ontap_info.snapshot_info }}"
    when:
    - ontap.ontap_info.snapshot_info[item].volume == volname
  - set_fact:
      newest: "{{ time|max }}"
  - set_fact:
      snapshot: "{{ ontap.ontap_info.snapshot_info[item].name }}"
    with_items: "{{ ontap.ontap_info.snapshot_info }}"
    when:
    - ontap.ontap_info.snapshot_info[item].access_time == newest
    - ontap.ontap_info.snapshot_info[item].volume == volname
  - name: Restore snapshot
    na_ontap_command:
      command: "['snap restore -vserver {{ vserver }} -volume {{ volname }} -snapshot {{ snapshot }}']"
      <<: *login

Most of the early parts of this playbook should look familiar with the exception of one line in the `vars`section.

vars:

  time: []

That `time` variable is initializing a list.  That list will be needed for when the creation times of the snapshots are being compared.  This set of tasks will add all the creation times to the list so that the most current can be selected.  The API actually stores the creation time in EPOCH which will make things easier by allowing the section of the newest snapshot simply by selecting the highest number.

Let’s look at each task one by one.

- name: Info
  na_ontap_info:
    state: info
    vserver: “{{ vserver }}”
    gather_subset: snapshot_info
    <<: *login
  register: ontap

Here the na_ontap_info module is being used to gather information, but only the snapshot_info subset and only from the vserver we have specified.  (* Additional limitation of return information could be achieved using the ‘query:’ argument with na_ontap_info )This information is being registered as the variable `ontap`.  If the `register` line isn’t added then the information won’t be available past this task.  The result is  data that looks like this (You can run a na_ontap_info playbook with ‘-vvv’ to see the output you will get for your run)


"ontap_info": {
        "ontap_version": "170",
        "ontapi_version": "170",
        "snapshot_info": {
        "ansible_lab-01:vol0:hourly.0": {
                "access_time": "1591732808",
                "afs_used": "287211520",
                "busy": "false",
                "compress_savings": "0",
                "compression_type": "none",
                "cumulative_percentage_of_total_blocks": "1",
                "cumulative_percentage_of_used_blocks": "2",
                "cumulative_total": "11348",
                "dedup_savings": "0",
                "dependency": null,
                "inofile_version": "4",
                "is_7_mode_snapshot": "true",
                "is_constituent_snapshot": "false",
                "name": "hourly.0",
                "percentage_of_total_blocks": "1",
                "percentage_of_used_blocks": "2",
                "performance_metadata": "1478656",
                "snapshot_instance_uuid": "b5e6c7d4-e4f0-4a68-80a1-5dd62e4aebcf",
                "snapshot_version_uuid": "b5e6c7d4-e4f0-4a68-80a1-5dd62e4aebcf",
                "total": "11348",
                "vbn0_savings": "0",
                "volume": "vol0",
                "volume_provenance_uuid": "884c70fa-d217-4b75-90f3-2a559bc44231",
                "vserver": "ansible_lab-01"
            },
            "ansible_lab-01:vol0:nightly.0": {
                "access_time": "1591747225",
                "afs_used": "0",
                "busy": "false",
                "compress_savings": "0",
                "compression_type": "none",
                "cumulative_percentage_of_total_blocks": "9",
                "cumulative_percentage_of_used_blocks": "11",
                "cumulative_total": "70720",
                "dedup_savings": "0",
                "dependency": null,
                "inofile_version": "4",
                "is_7_mode_snapshot": "true",
                "is_constituent_snapshot": "false",
                "name": "nightly.0",
                "percentage_of_total_blocks": "7",
                "percentage_of_used_blocks": "10",
                "performance_metadata": "0",
                "snapshot_instance_uuid": "d410b7bf-ead4-4d22-9880-8442ce33a5be",
                "snapshot_version_uuid": "d410b7bf-ead4-4d22-9880-8442ce33a5be",
                "total": "59372",
                "vbn0_savings": "0",
                "volume": "vol0",
                "volume_provenance_uuid": "884c70fa-d217-4b75-90f3-2a559bc44231",
                "vserver": "ansible_lab-01"
            },

 

The amount of information returned will depend on how many snapshots you have.  This output contains two of the snapshots from my info gather:

  • ansible_lab-01:vol0:hourly.0

and

  • ansible_lab-01:vol0:nightly.0

These are the hourly.0 and nightly.0 snaps on vol0 on the node SVM ansible_lab-01.
These names aren’t very usable unless you want to do string splitting and or REGEX expressions.  The usable parts are in the sub sections.  So let’s look at the sections in that example.

Section 1:

ontap_info’ – This section contains the information and variables ontap_version and ontapi_version as well as the snapshot_info section:

Section 2:

snapshot_info’ – This section contains all of the snapshots that exist on the cluster at the time of the information gather.  Each full snapshot name (ansible_lab-01:vol0:nightly.0) is also a section that contains the following variables.

Section 3.
(unique snapshot name)
access_time
afs_used
busy
compress_savings
compression_type
cumulative_percentage_of_total_blocks
cumulative_percentage_of_used_blocks
cumulative_total
dedup_savings
dependency
inofile_version
is_7_mode_snapshot
is_constituent_snapshot
name
percentage_of_total_blocks
percentage_of_used_blocks
performance_metadata
snapshot_instance_uuid
snapshot_version_uuid
total
vbn0_savings
volume
volume_provenance_uuid
vserver

These individual variables can be referenced by calling the sections in order plus the variable that you want for that particular snapshot.  For example:

“{{ ontap.ontap_info.snapshot_info[ansible_lab-01:vol0:hourly.0].name }}”

This would display the name of the snapshot for snapshot:

ansible_lab-01:vol0:hourly.0 

Snapshot “ansible_lab-01:vol0:hourly.0” is in “[]” because it is an entity of snapshot_info in addition to being a section.

The first “ontap” is added because that is what the task was registered as, so we still have to include it even though it isn’t a section per say of the returned data.  But how do you know the unique snapshot names?

If you knew that with certainty you wouldn’t need a playbook.  To help with this, we can loop through the results and store the information we want to work with.  This brings us to the next task of the playbook.

- set_fact:
      time: "{{ time }} + ['{{ ontap.ontap_info.snapshot_info[item].access_time }}' ]"
    loop: "{{ ontap.ontap_info.snapshot_info }}"
    when:
    - ontap.ontap_info.snapshot_info[item].volume == volname

Now we have the first of several tasks that use the `set_fact` module.  This is a core Ansible module that allows information to be set as variables.  These variables are even available to be shared between playbooks and roles, though only at the command line.

It’s a different module when using Tower.  If creating playbooks to be used in Tower use `set_stats` instead.  This first ‘fact’ we are setting is looping through all the unique snapshot names in snapshot_info .  We can reference them with the variable [item] even though we don’t know what they will be.

Then we have conditional statements in the ‘when’ section so that we are only adding to our time list when the volume variabls match the variable we are using for volume.

This is adding the variable section ‘access_time’ to the list.  The access_time is the time the snapshot was created.  One of the querks of the API is that the command line and API don’t always use the same terms or output.  Had this been at the command line, the field is called ‘create-time’.  Once we finish our loop, we can now make note of the largest number since that will be the most current snapshot.

- set_fact:
     newest: "{{ time|max }}"

Here we are using an Ansible filter |max to select the largest integer in the list, and using the set_factmodule to set that as the variable ‘newest’.

Once we have this, we can finally get the newest snapshot name for our volume no matter how it was named.

- set_fact:
      snapshot: "{{ ontap.ontap_info.snapshot_info[item].name }}"
    with_items: "{{ ontap.ontap_info.snapshot_info }}"
    when:
    - ontap.ontap_info.snapshot_info[item].access_time == newest
    - ontap.ontap_info.snapshot_info[item].volume == volname

This ‘set fact’ is using another loop of the snapshot_info data, but this time only setting the fact when the vserver, volume, and access_time match the proper inputs.  Once that is recorded the restore can be run.

- restore snapshot   
  na_ontap_command:
      command: "['snap restore -vserver {{ vserver }} -volume {{ volname }} -snapshot {{ snapshot }}']"
      <<: *login

*The na_ontap_command module must be used here as there isn’t actually an API for doing snapshot restores that is exposed.

There you have it.  The data from na_ontap_info in an array that can be accessed rather simply once you know what you are looking for and at.  This method can be used to auto select aggregates, or nodes, or interfaces or almost anything else that has unique parts you need to compare.

Be sure to check back frequently at www.netapp.io for the newest posts from our team, and as always join us on Slack in the #configurationmgmt channel to ask any questions or just say “Hi”.

 

 

 

 

 

 

 

 

David Blackwell on Linkedin
David Blackwell
Technical Marketing Engineer at NetApp
David is a twenty year IT veteran who has been an admin for just about every aspect of a DataCenter at one time or another. When not working, or tinkering with new software at home, David spends most of his free time with his five year old son and his lovely wife.

Pin It on Pinterest