If you have followed the SR OS with Ansible - 101 article you have some familiarity with well structured Ansible playbooks. Sometimes, however, it is useful to have a single, simple, Ansible playbook in your pocket to perform tasks that you may do regularly during development cycles.
This article provides one such simple playbook that can be executed to obtain configuration or state information from your SR OS device with model-driven mode enabled.
This article is written in the form of a tutorial. It assumes that you have access to one or more Nokia SR OS 7750/7950 routers (or vSIMs) and that you have a valid license for these products (if you need any of these, please contact your Nokia representative). It also assumes that you have access to a Linux machine from which to work (although it is possible to manage your SR OS devices from other host operating systems).
Equipment used for the creation of this article:
Item | Version |
---|---|
Linux Machine | CentOS 7.8 |
xmllint | libxml version 20901 |
jq | 1.6 |
Ansible | 2.11 |
SR OS | 23.3.R2 |
Create sros-ansible-get.yml playbook shown below using the editor of your choice. This playbook provides the ability to obtain SR OS configuration or state information using the NETCONF model-driven interface and output it in either JSON, XML or YAML formats.
Please note: The JSON interpreter in Ansible does not conform to RFC 7951 for the conversion of YANG modelled types to JSON and so the type definitions of elements should not be relied upon.
---
### Playbook runs on all hosts in the inventory regardless of their group
- hosts: all
### Do not gather facts from the devices
gather_facts: false
vars:
### Provide the username and password for the NETCONF SR OS user
- ansible_ssh_user: netconf
- ansible_ssh_pass: nokia123
### Streamline SSH operations. Alter as required for a more secure environment
- ansible_ssh_common_args: '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- ansible_host_key_auto_add: yes
- ansible_host_key_checking: False
- ansible_connection: ansible.netcommon.netconf
- ansible_network_os: <network-os>
tasks:
### JSON STATE
- block:
- name: Get state in JSON format
connection: netconf
netconf_get:
display: json
filter: <state xmlns="urn:nokia.com:sros:ns:yang:sr:state"/>
register: netconf_facts_state_json
- name: Output state info to file
copy:
content: "{{ netconf_facts_state_json.output.data | to_nice_json }}"
dest: "{{ inventory_hostname}}.json"
delegate_to: localhost
when: netconf_facts_state_json is defined
- debug:
msg: "Warning: Ansible JSON processing does not conform to RFC 7951"
- debug:
msg: "{{ inventory_hostname }}.json created"
tags: ['never','json-state','state-json']
### JSON CONFIG
- block:
- name: Get config in JSON format
connection: netconf
netconf_get:
display: json
filter: <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf"/>
register: netconf_facts_config_json
- name: Output config to file
copy:
content: "{{ netconf_facts_config_json.output.data | to_nice_json }}"
dest: "{{ inventory_hostname}}.json"
delegate_to: localhost
when: netconf_facts_config_json is defined
- debug:
msg: "Warning: Ansible JSON processing does not conform to RFC 7951"
- debug:
msg: "{{ inventory_hostname }}.json created"
tags: ['never','json-config','config-json']
### YAML STATE
- block:
- name: Get state in JSON format
connection: netconf
netconf_get:
display: json
filter: <state xmlns="urn:nokia.com:sros:ns:yang:sr:state"/>
register: netconf_facts_state_json
- name: Output state info to file converted to YAML
copy:
content: "{{ netconf_facts_state_json.output.data | to_nice_yaml }}"
dest: "{{ inventory_hostname}}.yaml"
delegate_to: localhost
when: netconf_facts_state_json is defined
- debug:
msg: "Warning: Ansible JSON processing does not conform to RFC 7951 which will effect the YAML output"
- debug:
msg: "{{ inventory_hostname }}.yaml created"
tags: ['never','yaml-state','state-yaml','yml-state','state-yml']
### YAML CONFIG
- block:
- name: Get config in JSON format
connection: netconf
netconf_get:
display: json
filter: <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf"/>
register: netconf_facts_config_json
- name: Output config to file converted to YAML
copy:
content: "{{ netconf_facts_config_json.output.data | to_nice_yaml }}"
dest: "{{ inventory_hostname}}.yaml"
delegate_to: localhost
when: netconf_facts_config_json is defined
- debug:
msg: "Warning: Ansible JSON processing does not conform to RFC 7951 which will effect the YAML output"
- debug:
msg: "{{ inventory_hostname }}.yaml created"
tags: ['never','yaml-config','config-yaml','yml-config','config-yml']
### XML STATE
- block:
- name: Get state in XML format
connection: netconf
netconf_get:
display: xml
filter: <state xmlns="urn:nokia.com:sros:ns:yang:sr:state"/>
register: netconf_facts_state_xml
- name: Pretty format XML
shell:
cmd: "xmllint --xpath /data/state - | xmllint --format -"
stdin: "{{ netconf_facts_state_xml.output }}"
register: netconf_facts_state_xml_formatted
delegate_to: localhost
- name: Output state info to file
copy:
content: "{{ netconf_facts_state_xml_formatted.stdout }}"
dest: "{{ inventory_hostname}}.xml"
delegate_to: localhost
when: netconf_facts_state_xml_formatted is defined
- debug:
msg: "{{ inventory_hostname }}.xml created"
tags: ['never','xml-state','state-xml']
### XML CONFIG
- block:
- name: Get config in XML format
connection: netconf
netconf_get:
display: xml
filter: <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf"/>
register: netconf_facts_config_xml
- name: Pretty format XML
shell:
cmd: "xmllint --xpath /data/configure - | xmllint --format -"
stdin: "{{ netconf_facts_config_xml.output }}"
register: netconf_facts_config_xml_formatted
delegate_to: localhost
- name: Output config info to file
copy:
content: "{{ netconf_facts_config_xml_formatted.stdout }}"
dest: "{{ inventory_hostname}}.xml"
delegate_to: localhost
when: netconf_facts_config_xml_formatted is defined
- debug:
msg: "{{ inventory_hostname }}.xml created"
tags: ['never','xml-config','config-xml']
The playbook is split into blocks and each block is selected at runtime by providing a tag to the ansible-playbook command. Providing no tags will not execute any of the playbook and will return no output.
The following tags can be provided to the ansible-playbook command at runtime:
Tag | Datastore | Output Style |
---|---|---|
state-xml | state | Formatted XML |
xml-state | state | Formatted XML |
config-xml | configuration | Formatted XML |
xml-config | configuration | Formatted XML |
state-json | state | Formatted JSON |
json-state | state | Formatted JSON |
config-json | configuration | Formatted JSON |
json-config | configuration | Formatted JSON |
state-yaml | state | YAML |
state-yml | state | YAML |
yaml-state | state | YAML |
yml-state | state | YAML |
config-yaml | configuration | YAML |
config-yml | configuration | YAML |
yaml-config | configuration | YAML |
yml-config | configuration | YAML |
The tags are provided in either order to provide an enhanced user experience, for example, state-xml and xml-state.
The never tag in the playbook is a special tag to alter the default behaviour of Ansible. By default, if no tags are specified at execution time then Ansible will execute all plays. The addition of the never tag means that plays will only be executed if the tag is specifically provided.
The playbook takes a standard Ansible inventory file as it's input and will execute the required functions on all devices in the inventory.
The username and password of the SR OS user with NETCONF access can be defined in the playbook (as above with 'netconf' and 'nokia123' as the example) or within the inventory file on a per node basis (as shown for device 'test2' below). These are defined as the fields ansible_ssh_user and ansible_ssh_pass respectively.
Example inventory file:
[routers]
test1 ansible_host=172.16.123.1
test2 ansible_host=172.16.123.2 ansible_ssh_user=netconf ansible_ssh_pass=nokia123
Executing the playbook is performed using ansible-playbook command and passing it the inventory file and the tags you require (see above). Use the following flags to pass the arguments:
Here is an example execution:
ansible-playbook -i inventory sros-ansible-get.yml -t json-config
Which yields the following output:
PLAY [all] *********************************************************************************************************************************************************
TASK [Get config in JSON format] ***********************************************************************************************************************************
ok: [test1]
ok: [test2]
TASK [Output config to file] ***************************************************************************************************************************************
changed: [test1 -> localhost]
changed: [test2 -> localhost]
TASK [debug] *******************************************************************************************************************************************************
ok: [test1] => {
"msg": "Warning: Ansible JSON processing does not conform to RFC 7951"
}
ok: [test2] => {
"msg": "Warning: Ansible JSON processing does not conform to RFC 7951"
}
TASK [debug] *******************************************************************************************************************************************************
ok: [test1] => {
"msg": "test1.json created"
}
ok: [test2] => {
"msg": "test2.json created"
}
PLAY RECAP *********************************************************************************************************************************************************
test1 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test2 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The resultant output is a set of filenames with the same name as the router name in the inventory file. Each file has the appropriate extension depending on the required output (.json, .yaml, .xml).
Having generated these output files it is often useful to query the data (state or configuration) for specific branches or leaves.
Using the JSON formatted output from this Ansible playbook and the jq tool available on most Linux distributions you can quickly integrate the information and return specific elements that you are interested in.
Here are some examples that use the JSON output from the configuration of the test1 router:
Identify the configured hostname
jq '.configure.system.name' test1.json
Returns:
"test1"
Identify the configured NTP information
jq '.configure.system.time.ntp' test1.json
Returns:
{
"admin-state": "enable",
"authentication-check": "false",
"server": {
"ip-address": "172.16.123.254",
"router-instance": "management"
}
}
Here are some examples that use the JSON output from the state of the test1 router:
Obtain all user statistics on the router
jq '.state.system.security["user-params"]["local-user"]' test1.json
Returns:
{
"user": [
{
"attempted-logins": "11",
"failed-logins": "0",
"locked-out": "false",
"password-changed-time": "2019-11-01T16:22:31.0Z",
"user-name": "admin"
},
{
"attempted-logins": "0",
"failed-logins": "0",
"locked-out": "false",
"password-changed-time": "2019-11-01T16:22:33.0Z",
"user-name": "grpc"
},
{
"attempted-logins": "1477",
"failed-logins": "0",
"locked-out": "false",
"password-changed-time": "2019-11-01T16:22:33.0Z",
"user-name": "netconf"
}
]
}
Please note: Many JSON tools, including jq, are unable to parse the '-' character without quoting it. This is the reason that in the above example the jq filter is '.state.system.security["user-params"]["local-user"]' rather than '.state.system.security.user-params.local-user'