From e90f1d6449d2754247ee093d991a3c9a83a3a315 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Fri, 2 Jun 2017 22:03:27 +0530 Subject: [PATCH] net_command platform agnostic module (#25249) * net_command platform agnostic implementation Signed-off-by: Trishna Guha * Add net_command platform agnostic module Signed-off-by: Trishna Guha * Add integration test for net_command module Signed-off-by: Trishna Guha * fix yaml issue --- lib/ansible/modules/network/net_command.py | 131 ++++++++++++++++++ lib/ansible/plugins/action/net_command.py | 26 ++++ test/integration/net_command.yml | 7 - test/integration/platform_agnostic.yaml | 1 + test/integration/targets/net_command/aliases | 1 + .../targets/net_command/defaults/main.yaml | 2 + .../targets/net_command/tasks/cli.yaml | 16 +++ .../targets/net_command/tasks/main.yaml | 2 + .../net_command/tests/cli/contains.yaml | 19 +++ .../net_command/tests/eos/contains.yaml | 20 +++ .../net_command/tests/ios/contains.yaml | 19 +++ .../net_command/tests/iosxr/contains.yaml | 19 +++ .../net_command/tests/nxos/contains.yaml | 19 +++ .../net_command/tests/vyos/contains.yaml | 21 +++ 14 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 lib/ansible/modules/network/net_command.py create mode 100644 lib/ansible/plugins/action/net_command.py delete mode 100644 test/integration/net_command.yml create mode 100644 test/integration/targets/net_command/aliases create mode 100644 test/integration/targets/net_command/defaults/main.yaml create mode 100644 test/integration/targets/net_command/tasks/cli.yaml create mode 100644 test/integration/targets/net_command/tasks/main.yaml create mode 100644 test/integration/targets/net_command/tests/cli/contains.yaml create mode 100644 test/integration/targets/net_command/tests/eos/contains.yaml create mode 100644 test/integration/targets/net_command/tests/ios/contains.yaml create mode 100644 test/integration/targets/net_command/tests/iosxr/contains.yaml create mode 100644 test/integration/targets/net_command/tests/nxos/contains.yaml create mode 100644 test/integration/targets/net_command/tests/vyos/contains.yaml diff --git a/lib/ansible/modules/network/net_command.py b/lib/ansible/modules/network/net_command.py new file mode 100644 index 0000000000..c86a75daef --- /dev/null +++ b/lib/ansible/modules/network/net_command.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Ansible by Red Hat, inc +# +# This file is part of Ansible by Red Hat +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'core'} + +DOCUMENTATION = """ +--- +module: net_command +version_added: "2.4" +author: "Trishna Guha (@trishnag)" +short_description: Run arbitrary commands on a network device +description: + - Sends an arbitrary set of commands to network node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. +options: + commands: + description: + - The commands to send to the remote network device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of I(retries) has been exceeded. + required: true + wait_for: + description: + - Specifies what to evaluate from the output of the command + and what conditionals to apply. This argument will cause + the task to wait for a particular conditional to be true + before moving forward. If the conditional is not true + by the configured retries, the task fails. See examples. + required: false + default: null + aliases: ['waitfor'] + version_added: "2.2" + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the I(wait_for) must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + required: false + default: all + choices: ['any', 'all'] + version_added: "2.2" + retries: + description: + - Specifies the number of retries a command should be tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the I(wait_for) + conditionals. + required: false + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditional, the interval indicates how to long to wait before + trying the command again. + required: false + default: 1 +""" + +EXAMPLES = """ +- name: run show version on remote devices + net_command: + commands: show version +- name: run show version and check to see if output contains Network vendor name + net_command: + commands: show version + wait_for: result[0] contains Network vendor name +- name: run multiple commands on remote nodes + net_command: + commands: + - show version + - show interfaces +- name: run multiple commands and evaluate the output + net_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains Network vendor name + - result[1] contains Loopback0 +- name: run commands and specify the output format + net_command: + commands: + - command: show version + output: json +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" diff --git a/lib/ansible/plugins/action/net_command.py b/lib/ansible/plugins/action/net_command.py new file mode 100644 index 0000000000..a4ee4db0b6 --- /dev/null +++ b/lib/ansible/plugins/action/net_command.py @@ -0,0 +1,26 @@ +# (c) 2017, Ansible Inc, +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action.net_base import ActionModule as _ActionModule + + +class ActionModule(_ActionModule): + def run(self, tmp=None, task_vars=None): + result = super(ActionModule, self).run(tmp, task_vars) + return result diff --git a/test/integration/net_command.yml b/test/integration/net_command.yml deleted file mode 100644 index 958d096136..0000000000 --- a/test/integration/net_command.yml +++ /dev/null @@ -1,7 +0,0 @@ -- hosts: - - vyos - - junos - - ios - gather_facts: false - roles: - - { role: net_command, tags: net_command } diff --git a/test/integration/platform_agnostic.yaml b/test/integration/platform_agnostic.yaml index eda4f5431d..b263ffcd0e 100644 --- a/test/integration/platform_agnostic.yaml +++ b/test/integration/platform_agnostic.yaml @@ -10,3 +10,4 @@ roles: - { role: net_system, when: "limit_to in ['*', 'net_system']" } - { role: net_banner, when: "limit_to in ['*', 'net_banner']" } + - { role: net_command, when: "limit_to in ['*', 'net_command']" } diff --git a/test/integration/targets/net_command/aliases b/test/integration/targets/net_command/aliases new file mode 100644 index 0000000000..93151a8d9d --- /dev/null +++ b/test/integration/targets/net_command/aliases @@ -0,0 +1 @@ +network/ci diff --git a/test/integration/targets/net_command/defaults/main.yaml b/test/integration/targets/net_command/defaults/main.yaml new file mode 100644 index 0000000000..5f709c5aac --- /dev/null +++ b/test/integration/targets/net_command/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/net_command/tasks/cli.yaml b/test/integration/targets/net_command/tasks/cli.yaml new file mode 100644 index 0000000000..46d86dd698 --- /dev/null +++ b/test/integration/targets/net_command/tasks/cli.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/net_command/tasks/main.yaml b/test/integration/targets/net_command/tasks/main.yaml new file mode 100644 index 0000000000..415c99d8b1 --- /dev/null +++ b/test/integration/targets/net_command/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/net_command/tests/cli/contains.yaml b/test/integration/targets/net_command/tests/cli/contains.yaml new file mode 100644 index 0000000000..8a6dfbb8c5 --- /dev/null +++ b/test/integration/targets/net_command/tests/cli/contains.yaml @@ -0,0 +1,19 @@ +--- +- debug: msg="START cli/contains.yaml" + +- include: "{{ role_path }}/tests/ios/contains.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'ios' + +- include: "{{ role_path }}/tests/iosxr/contains.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'iosxr' + +- include: "{{ role_path }}/tests/nxos/contains.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'nxos' + +- include: "{{ role_path }}/tests/eos/contains.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'eos' + +- include: "{{ role_path }}/tests/vyos/contains.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'vyos' + +- debug: msg="END cli/contains.yaml" diff --git a/test/integration/targets/net_command/tests/eos/contains.yaml b/test/integration/targets/net_command/tests/eos/contains.yaml new file mode 100644 index 0000000000..8a55dd7a97 --- /dev/null +++ b/test/integration/targets/net_command/tests/eos/contains.yaml @@ -0,0 +1,20 @@ +--- +- debug: msg="START eos/contains.yaml" + +- name: test contains operator + net_command: + commands: + - show version + - show interface Management1 | json + wait_for: + - "result[0] contains EOS" + - "result[1].interfaces.Management1.name contains Manage" + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- debug: msg="END eos/contains.yaml" diff --git a/test/integration/targets/net_command/tests/ios/contains.yaml b/test/integration/targets/net_command/tests/ios/contains.yaml new file mode 100644 index 0000000000..babe82a824 --- /dev/null +++ b/test/integration/targets/net_command/tests/ios/contains.yaml @@ -0,0 +1,19 @@ +--- +- debug: msg="START ios/contains.yaml" + +- name: test contains operator + net_command: + commands: + - show version + - show interface loopback 888 + wait_for: + - "result[0] contains Cisco" + - "result[1] contains Loopback888" + register: result + +- assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- debug: msg="END ios/contains.yaml" diff --git a/test/integration/targets/net_command/tests/iosxr/contains.yaml b/test/integration/targets/net_command/tests/iosxr/contains.yaml new file mode 100644 index 0000000000..eefe27b76f --- /dev/null +++ b/test/integration/targets/net_command/tests/iosxr/contains.yaml @@ -0,0 +1,19 @@ +--- +- debug: msg="START iosxr/contains.yaml" + +- name: test contains operator + net_command: + commands: + - show version + - show interfaces Loopback 888 + wait_for: + - "result[0] contains 'Cisco IOS XR Software'" + - "result[1] contains 'Hardware is Loopback interface'" + register: result + +- assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- debug: msg="END iosxr/contains.yaml" diff --git a/test/integration/targets/net_command/tests/nxos/contains.yaml b/test/integration/targets/net_command/tests/nxos/contains.yaml new file mode 100644 index 0000000000..9bd387d5b1 --- /dev/null +++ b/test/integration/targets/net_command/tests/nxos/contains.yaml @@ -0,0 +1,19 @@ +--- +- debug: msg="START nxos/contains.yaml" + +- name: test contains operator + net_command: + commands: + - show version + - show interface mgmt0 | json + wait_for: + - "result[0] contains NX-OS" + - "result[1].TABLE_interface.ROW_interface.interface contains mgmt" + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- debug: msg="END nxos/contains.yaml" diff --git a/test/integration/targets/net_command/tests/vyos/contains.yaml b/test/integration/targets/net_command/tests/vyos/contains.yaml new file mode 100644 index 0000000000..21f6df8f26 --- /dev/null +++ b/test/integration/targets/net_command/tests/vyos/contains.yaml @@ -0,0 +1,21 @@ +--- +- debug: msg="START vyos/contains.yaml" + +- name: test contains operator + net_command: + commands: + - show version + - show interface + wait_for: + - result[0] contains VyOS + - result[1] contains eth0 + provider: "{{ cli }}" + register: result + +- assert: + that: + - result.changed == false + - result.stdout is defined + - result.stdout_lines is defined + +- debug: msg="END vyos/contains.yaml"