From 23fc144d0b0466297ea85e40e98da9be22d8ead0 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:33:49 +0530 Subject: [PATCH 01/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- library/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 library/__init__.py diff --git a/library/__init__.py b/library/__init__.py new file mode 100644 index 0000000..e69de29 From f12fff958c479c6b9d09e46e6e3cec33e0e9cefe Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:34:02 +0530 Subject: [PATCH 02/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 88 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 library/ios_facts.py diff --git a/library/ios_facts.py b/library/ios_facts.py new file mode 100644 index 0000000..ec19228 --- /dev/null +++ b/library/ios_facts.py @@ -0,0 +1,88 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The module file for ios_facts +""" + +from __future__ import absolute_import, division, print_function +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.ios.facts.facts import Facts + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': [u'preview'], + 'supported_by': [u'Ansible Network']} + + +DOCUMENTATION = """ +--- +module: ios_facts +version_added: 2.9 +short_description: Collect facts from remote devices running Cisco IOS +description: + - Collects a base set of device facts from a remote device that + is running IOS. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +author: [u'Sumit Jaiswal (@justjais)'] +notes: + - Tested against IOS Version 15.6(3)M2 on VIRL +options: + gather_subset: + description: + - When supplied, this argument restricts the facts collected + to a given subset. + - Possible values for this argument include + C(all), C(hardware), C(config), and C(interfaces). + - Specify a list of values to include a larger subset. + - Use a value with an initial C(!) to collect all facts except that subset. + required: false + default: '!config' +""" + +EXAMPLES = """ +# Gather all facts +- ios_facts: + gather_subset: all + +# Collect only the interfaces and default facts +- ios_facts: + gather_subset: + - config + +# Do not collect interfaces facts +- ios_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +See the respective resource module parameters for the tree. +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.ios.facts.facts import Facts + + +def main(): + """ + Main entry point for module execution + + :returns: ansible_facts + """ + module = AnsibleModule(argument_spec=Facts.argument_spec, + supports_check_mode=True) + warnings = list() + + connection = Connection(module._socket_path) #pylint: disable=W0212 + gather_subset = module.params['gather_subset'] + ansible_facts = Facts().get_facts(module, connection, gather_subset) + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + +if __name__ == '__main__': + main() From 5ff786f33f350aaca22c632178da8c6889a4a77d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:34:34 +0530 Subject: [PATCH 03/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- library/ios_interfaces.py | 183 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 library/ios_interfaces.py diff --git a/library/ios_interfaces.py b/library/ios_interfaces.py new file mode 100644 index 0000000..c8aebff --- /dev/null +++ b/library/ios_interfaces.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################## +################# WARNING #################### +############################################## +### +### This file is auto generated by the resource +### module builder playbook. +### +### Do not edit this file manually. +### +### Changes to this file will be over written +### by the resource module builder. +### +### Changes should be made in the model used to +### generate this file or in the resource module +### builder template. +### +############################################## +############################################## +############################################## + +""" +The module file for ios_interfaces +""" + +from __future__ import absolute_import, division, print_function +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ios.config.interfaces.interfaces import Interfaces + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': [u'preview'], + 'supported_by': [u'Ansible Network']} + + +DOCUMENTATION = """ +--- +module: ios_interfaces +version_added: 2.9 +short_description: Manage Interface on Cisco IOS network devices. +description: Manage Interface on Cisco IOS network devices. +author: [u'Sumit Jaiswal (@justjais)'] +options: + config: + description: The provided configuration + suboptions: + name: + description: + - Name of interface, i.e. GigabitEthernet0/2, loopback999. + type: str + required: true + description: + description: + - Description of Interface. + type: str + enabled: + description: + - Interface link status. + type: bool + default: true + speed: + description: + - Interface link speed. Applicable for ethernet interface only. + type: str + mtu: + description: + - Maximum size of transmit packet. Must be an number between 64 and 9600. + Applicable for Ethernet interface only. + type: str + duplex: + description: + - Interface link status. Applicable for ethernet interface only. + type: str + default: auto + choices: ['full', 'half', 'auto'] + state: + choices: + - merged + - replaced + - overridden + - deleted + default: merged + description: + - The state the configuration should be left in + type: str +""" + +EXAMPLES = """ +--- + +# Using merged + +- name: Configure Ethernet interfaces + ios_interfaces: + config: + - name: GigabitEthernet0/2 + description: 'Configured by Ansible' + enabled: True + - name: GigabitEthernet0/3 + description: 'Configured by Ansible Network' + enabled: False + duplex: full + operation: merged + +# Using replaced + +- name: Configure following interfaces and replace their existing config + ios_interfaces: + config: + - name: GigabitEthernet0/2 + description: Configured by Ansible + enabled: True + mtu: 2000 + - name: GigabitEthernet0/3 + description: 'Configured by Ansible Network' + enabled: False + duplex: auto + operation: replaced + +# Using overridden + +- name: Override interfaces + ios_interfaces: + config: + - name: GigabitEthernet0/2 + description: 'Configured by Ansible' + enabled: True + duplex: auto + - name: GigabitEthernet0/3 + description: 'Configured by Ansible Network' + enabled: False + speed: 1000 + operation: overridden + +# Using deleted + +- name: Delete IOS interfaces as in given arguments + ios_interfaces: + config: + - name: GigabitEthernet0/2 + description: 'Configured by Ansible' + - name: GigabitEthernet0/3 + description: 'Configured by Ansible Network' + mtu: 1800 + operation: deleted + +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation + returned: always + sample: The configuration returned will alwys be in the same format of the paramters above. +after: + description: The resulting configuration model invocation + returned: when changed + sample: The configuration returned will alwys be in the same format of the paramters above. +commands: + description: The set of commands pushed to the remote device + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Interfaces.argument_spec, + supports_check_mode=True) + + result = Interfaces(module).execute_module() + module.exit_json(**result) + +if __name__ == '__main__': + main() + From afea203f2ac9d7718e292ffe4c70a33de769cf8e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:34:45 +0530 Subject: [PATCH 04/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/__init__.py diff --git a/module_utils/__init__.py b/module_utils/__init__.py new file mode 100644 index 0000000..e69de29 From ac7670d710d5a8349c007264ada66bb274da3bf9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:34:57 +0530 Subject: [PATCH 05/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/__init__.py diff --git a/module_utils/ios/__init__.py b/module_utils/ios/__init__.py new file mode 100644 index 0000000..e69de29 From a0eebb7300e1f915e32e8d07896461b2e0a5c8aa Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:35:07 +0530 Subject: [PATCH 06/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/__init__.py diff --git a/module_utils/ios/argspec/__init__.py b/module_utils/ios/argspec/__init__.py new file mode 100644 index 0000000..e69de29 From 921ed1d871fc0b701fa5fe88ca160b10aa92cbd6 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:35:19 +0530 Subject: [PATCH 07/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/facts/__init__.py diff --git a/module_utils/ios/argspec/facts/__init__.py b/module_utils/ios/argspec/facts/__init__.py new file mode 100644 index 0000000..e69de29 From bb50dc5f342824042a211d3d023cebdede20be5f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:35:33 +0530 Subject: [PATCH 08/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/facts.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 module_utils/ios/argspec/facts/facts.py diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py new file mode 100644 index 0000000..5a3a74f --- /dev/null +++ b/module_utils/ios/argspec/facts/facts.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The arg spec for the ios facts module. +""" + +class FactsArgs(object): + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'net_configuration_interfaces', + ] + + argument_spec = { + 'gather_subset': dict(default=['all'], choices=choices, type='list') + } + From e2fc5e78130f0919f36c00a96234beb6c531476b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:36:55 +0530 Subject: [PATCH 09/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/argspec/interfaces/__init__.py diff --git a/module_utils/ios/argspec/interfaces/__init__.py b/module_utils/ios/argspec/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 150f2481d2ca7cee10555ad7453ca62cb96d2a7b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:37:09 +0530 Subject: [PATCH 10/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- .../ios/argspec/interfaces/interfaces.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 module_utils/ios/argspec/interfaces/interfaces.py diff --git a/module_utils/ios/argspec/interfaces/interfaces.py b/module_utils/ios/argspec/interfaces/interfaces.py new file mode 100644 index 0000000..a5c1ea3 --- /dev/null +++ b/module_utils/ios/argspec/interfaces/interfaces.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +################# WARNING #################### +############################################## +### +### This file is auto generated by the resource +### module builder playbook. +### +### Do not edit this file manually. +### +### Changes to this file will be over written +### by the resource module builder. +### +### Changes should be made in the model used to +### generate this file or in the resource module +### builder template. +### +############################################## +############################################## +############################################## +""" +The arg spec for the ios_interfaces module +""" + +class InterfacesArgs(object): + + def __init__(self, **kwargs): + pass + + config_spec = { + 'name': dict(type='str', required=True), + 'description': dict(), + 'enabled': dict(default=True, type=bool), + 'speed': dict(), + 'mtu': dict(), + 'duplex': dict(choices=['full', 'half', 'auto']), + } + + argument_spec = { + 'state': dict(default='merged', choices=['merged', 'replaced', 'overriden', 'deleted']), + 'config': dict(type='list', elements='dict', options=config_spec) + } + From 321e71f9e97251445092d69c1947cf0734f74557 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:37:20 +0530 Subject: [PATCH 11/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/__init__.py diff --git a/module_utils/ios/config/__init__.py b/module_utils/ios/config/__init__.py new file mode 100644 index 0000000..e69de29 From e7d6a00724af931beff0f25933249beecae6fb9e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:38:13 +0530 Subject: [PATCH 12/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/config/interfaces/__init__.py diff --git a/module_utils/ios/config/interfaces/__init__.py b/module_utils/ios/config/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From a6b3b10146e7b62f10f1a0cae49d3be73d703c0e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:38:25 +0530 Subject: [PATCH 13/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- .../ios/config/interfaces/interfaces.py | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 module_utils/ios/config/interfaces/interfaces.py diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/ios/config/interfaces/interfaces.py new file mode 100644 index 0000000..6fab1e0 --- /dev/null +++ b/module_utils/ios/config/interfaces/interfaces.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The ios_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + + +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common.utils import to_list + +from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.ios.config.base import ConfigBase +from ansible.module_utils.ios.facts.facts import Facts +from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list +import q + +class Interfaces(ConfigBase, InterfacesArgs): + """ + The ios_interfaces class + """ + + gather_subset = [ + 'net_configuration_interfaces', + ] + + def get_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts = Facts().get_facts(self._module, self._connection, self.gather_subset) + interfaces_facts = facts['net_configuration'].get('interfaces') + if not interfaces_facts: + return [] + return interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from moduel execution + """ + result = {'changed': False} + commands = list() + warnings = list() + + existing_facts = self.get_interface_facts() + + commands.extend(self.set_config(existing_facts)) + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + interfaces_facts = self.get_interfaces_facts() + + result['before'] = interfaces_facts + if result['changed']: + result['after'] = interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the deisred configuration + """ + want = self._module.params['config'] + q(want) + for w in want: + w.update({'name': normalize_interface(w['name'])}) + have = existing_facts#self.get_interfaces_facts() + resp = self.set_state(want, have) + q(resp) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the deisred configuration + """ + commands = list() + + state = self._module.params['state'] + if state == 'overridden': + commands = self._state_overridden(want, have) + else: + for w in want: + name = w['name'] + interface_type = get_interface_type(name) + obj_in_have = search_obj_in_list(name, have) + + if state == 'deleted': + commands.extend(self._state_deleted(w, obj_in_have, interface_type)) + + if state == 'merged': + commands.extend(self._state_merged(w, obj_in_have, interface_type)) + + if state == 'replaced': + commands.extend(self._state_replaced(w, obj_in_have, interface_type)) + return commands + + @staticmethod + def _state_replaced(want, obj_in_have, interface_type): + """ The command generator when state is replaced + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the deisred configuration + """ + commands = list() + if interface_type in ('loopback', 'portchannel', 'svi'): + commands.append('no interface {0}'.format(want['name'])) + commands.extend(Interfaces._state_merged(want, obj_in_have, interface_type)) + else: + commands.append('default interface {0}'.format(want['name'])) + commands.extend(Interfaces._state_merged(want, obj_in_have, interface_type)) + # if want != have: + # # compare and generate command set + # pass + + return commands + + @staticmethod + def _state_overridden(want, have): + """ The command generator when state is overridden + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the deisred configuration + """ + # if want != have: + # pass + commands = list() + + for h in have: + name = h['name'] + obj_in_want = search_obj_in_list(name, want) + if not obj_in_want: + interface_type = get_interface_type(name) + if interface_type == 'ethernet': + default = True + if h['enabled'] is True: + keys = ('description', 'mode', 'mtu', 'speed', 'duplex') + for k, v in iteritems(h): + if k in keys: + if h[k] is not None: + default = False + break + else: + default = False + + if default is False: + # Chan interface to its default state + commands.append('default interface {0}'.format(name)) + + return commands + + @staticmethod + def _state_merged(want, have, obj_in_have, interface_type): + """ The command generator when state is merged + + :param want: the additive configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + + commands = [] + + args = ('speed', 'description', 'duplex', 'mtu') + name = want['name'] + mode = want.get('mode') + + if name: + interface = 'interface ' + name + + if not obj_in_have: + commands.append(interface) + if interface_type in ('ethernet', 'portchannel'): + pass + + return commands + + @staticmethod + def _state_deleted(want, have): + """ The command generator when state is deleted + + :param want: the objects from which the configuration should be removed + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + if want != have: + pass + commands = [] + return commands + + From 736b6cbeddd5a0f1e4aea655876b3ba605e3cf6b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:38:41 +0530 Subject: [PATCH 14/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/facts/__init__.py diff --git a/module_utils/ios/facts/__init__.py b/module_utils/ios/facts/__init__.py new file mode 100644 index 0000000..e69de29 From f475bdaa52ecab7dd008c6ec4cb6da4d1a3ed76e Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:38:51 +0530 Subject: [PATCH 15/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/base.py | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 module_utils/ios/facts/base.py diff --git a/module_utils/ios/facts/base.py b/module_utils/ios/facts/base.py new file mode 100644 index 0000000..913df74 --- /dev/null +++ b/module_utils/ios/facts/base.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The ios facts base class +this contains methods common to all facts subsets +""" + +import re +from copy import deepcopy +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + """ + The ios facts base class + """ + generated_spec = {} + ansible_facts = {'net_configuration': {}} + + def __init__(self, argspec, subspec=None, options=None): + spec = deepcopy(argspec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = self.generate_dict(facts_argument_spec) + + @staticmethod + def generate_dict(spec): + """ + Generate dictionary which is in sync with argspec + + :param spec: A dictionary which the argspec of module + :rtype: A dictionary + :returns: A dictionary in sync with argspec with default value + """ + obj = {} + if not spec: + return obj + + for key, val in iteritems(spec): + if 'default' in val: + dct = {key: val['default']} + else: + dct = {key: None} + obj.update(dct) + + return obj + + @staticmethod + def parse_conf_arg(cfg, arg): + """ + Parse config based on argument + + :param cfg: A text string which is a line of configuration. + :param arg: A text string which is to be matched. + :rtype: A text string + :returns: A text string if match is found + """ + match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) + if match: + result = match.group(1).strip() + else: + result = None + return result + + @staticmethod + def parse_conf_cmd_arg(cfg, cmd, res1, res2=None): + """ + Parse config based on command + + :param cfg: A text string which is a line of configuration. + :param cmd: A text string which is the command to be matched + :param res1: A text string to be returned if the command is present + :param res2: A text string to be returned if the negate command is present + :rtype: A text string + :returns: A text string if match is found + """ + match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) + if match: + return res1 + if res2 is not None: + match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) + if match: + return res2 + return None + + @staticmethod + def generate_final_config(cfg_dict): + """ + Generate final config dictionary + + :param cfg_dict: A dictionary parsed in the facts system + :rtype: A dictionary + :returns: A dictionary by elimating keys that have null values + """ + final_cfg = {} + if not cfg_dict: + return final_cfg + + for key, val in iteritems(cfg_dict): + if val is not None: + final_cfg.update({key:val}) + return final_cfg From 7d1d333206d54a78743f517886465c9920f1b2b4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:39:03 +0530 Subject: [PATCH 16/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/facts.py | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 module_utils/ios/facts/facts.py diff --git a/module_utils/ios/facts/facts.py b/module_utils/ios/facts/facts.py new file mode 100644 index 0000000..1df0a0b --- /dev/null +++ b/module_utils/ios/facts/facts.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The facts class for ios +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from ansible.module_utils.ios.argspec.facts.facts import FactsArgs +from ansible.module_utils.ios.facts.base import FactsBase +from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs +from ansible.module_utils.ios.facts.interfaces.interfaces import InterfacesFacts + + +class Facts(FactsArgs, FactsBase): + """ The fact class for ios + """ + + def get_facts(self, module, connection, gather_subset=['all']): + """ Collect the facts for ios + + :param module: The module instance + :param connection: The device connection + :param gather_subset: The facts subset to collect + :rtype: dict + :returns: the facts gathered + """ + valid_subsets = self.argument_spec['gather_subset'].get('choices', []) + if valid_subsets and 'all' in valid_subsets: + valid_subsets.remove('all') + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(valid_subsets) + continue + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(valid_subsets) + continue + exclude = True + else: + exclude = False + + if subset not in valid_subsets: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(valid_subsets) + + runable_subsets.difference_update(exclude_subsets) + self.ansible_facts['gather_subset'] = list(runable_subsets) + + for attr in runable_subsets: + getattr(self, '_get_%s' % attr, {})(module, connection) + + return self.ansible_facts + + @staticmethod + def _get_net_configuration_interfaces(module, connection): + return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) + From f43926eb032ad916935eb5c94933ddc1b2358055 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:39:14 +0530 Subject: [PATCH 17/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/facts/interfaces/__init__.py diff --git a/module_utils/ios/facts/interfaces/__init__.py b/module_utils/ios/facts/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 1d8e35b68bce8cbb8d34636b0ff16aa27f5bdb4d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:39:31 +0530 Subject: [PATCH 18/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- .../ios/facts/interfaces/interfaces.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 module_utils/ios/facts/interfaces/interfaces.py diff --git a/module_utils/ios/facts/interfaces/interfaces.py b/module_utils/ios/facts/interfaces/interfaces.py new file mode 100644 index 0000000..4667eef --- /dev/null +++ b/module_utils/ios/facts/interfaces/interfaces.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The ios interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +import re +from copy import deepcopy + +from ansible.module_utils.ios.facts.base import FactsBase +from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface + + +class InterfacesFacts(FactsBase): + """ The ios interfaces fact class + """ + + def populate_facts(self, module, connection, data=None): + """ Populate the facts for interfaces + + :param module: the module instance + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + + if not data: + data = connection.get('show running-config | section ^interface') + # operate on a collection of resource x + config = data.split('interface ') + for conf in config: + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + facts = {} + + if objs: + facts['interfaces'] = objs + self.ansible_facts['net_configuration'].update(facts) + return self.ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + match = re.search(r'^(\S+)', conf) + intf = match.group(1) + + if get_interface_type(intf) == 'unknown': + return {} + # populate the facts from the configuration + config['name'] = normalize_interface(intf) + config['description'] = self.parse_conf_arg(conf, 'description') + config['speed'] = self.parse_conf_arg(conf, 'speed') + config['mtu'] = self.parse_conf_arg(conf, 'mtu') + config['duplex'] = self.parse_conf_arg(conf, 'duplex') + enabled = self.parse_conf_cmd_arg(conf, 'shutdown', False) + config['enabled'] = enabled if enabled is not None else config['enabled'] + + return self.generate_final_config(config) From 4357f00dd43b47ccc056e7e845adaa750a5ba1e1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:39:42 +0530 Subject: [PATCH 19/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/ios/utils/__init__.py diff --git a/module_utils/ios/utils/__init__.py b/module_utils/ios/utils/__init__.py new file mode 100644 index 0000000..e69de29 From b9d837cf1034424bd3ef3d83b66447f12f382220 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:39:56 +0530 Subject: [PATCH 20/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/utils/utils.py | 92 +++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 module_utils/ios/utils/utils.py diff --git a/module_utils/ios/utils/utils.py b/module_utils/ios/utils/utils.py new file mode 100644 index 0000000..7d4e7ac --- /dev/null +++ b/module_utils/ios/utils/utils.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# utils + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + return None + +def normalize_interface(name): + """Return the normalized interface name + """ + if not name: + return + + def _get_number(name): + digits = '' + for char in name: + if char.isdigit() or char in '/.': + digits += char + return digits + + if name.lower().startswith('gi'): + if_type = 'GigabitEthernet' + elif name.lower().startswith('te'): + if_type = 'TenGigabitEthernet' + elif name.lower().startswith('fa'): + if_type = 'FastEthernet' + elif name.lower().startswith('fo'): + if_type = 'FortyGigabitEthernet' + elif name.lower().startswith('et'): + if_type = 'Ethernet' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('lo'): + if_type = 'loopback' + elif name.lower().startswith('po'): + if_type = 'port-channel' + elif name.lower().startswith('nv'): + if_type = 'nve' + elif name.lower().startswith('twe'): + if_type = 'TwentyFiveGigE' + elif name.lower().startswith('hu'): + if_type = 'HundredGigE' + else: + if_type = None + + number_list = name.split(' ') + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + +def get_interface_type(interface): + """Gets the type of interface + """ + + if interface.upper().startswith('GI'): + return 'GigabitEthernet' + elif interface.upper().startswith('TE'): + return 'TenGigabitEthernet' + elif interface.upper().startswith('FA'): + return 'FastEthernet' + elif interface.upper().startswith('FO'): + return 'FortyGigabitEthernet' + elif interface.upper().startswith('ET'): + return 'Ethernet' + elif interface.upper().startswith('VL'): + return 'Vlan' + elif interface.upper().startswith('LO'): + return 'loopback' + elif interface.upper().startswith('PO'): + return 'port-channel' + elif interface.upper().startswith('NV'): + return 'nve' + elif interface.upper().startswith('TWE'): + return 'TwentyFiveGigE' + elif interface.upper().startswith('HU'): + return 'HundredGigE' + else: + return 'unknown' \ No newline at end of file From cce5308c7d247c21399327f099a567d4e5032433 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:40:11 +0530 Subject: [PATCH 21/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/network/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/network/__init__.py diff --git a/module_utils/network/__init__.py b/module_utils/network/__init__.py new file mode 100644 index 0000000..e69de29 From 6e06f00ac060f97649979cbe83adc01dce37cb3f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:40:21 +0530 Subject: [PATCH 22/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/network/argspec/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/network/argspec/__init__.py diff --git a/module_utils/network/argspec/__init__.py b/module_utils/network/argspec/__init__.py new file mode 100644 index 0000000..e69de29 From 6f905737db240e7d8eaf69bee16c3cd50e33d5bf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:40:32 +0530 Subject: [PATCH 23/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/network/argspec/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 module_utils/network/argspec/base.py diff --git a/module_utils/network/argspec/base.py b/module_utils/network/argspec/base.py new file mode 100644 index 0000000..596bbab --- /dev/null +++ b/module_utils/network/argspec/base.py @@ -0,0 +1,12 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" The argspec base class +""" + +class ArgspecBase(object): #pylint: disable=R0205,R0903 + """ The argspec base class + """ + def __init__(self, **kwargs): + pass From 981f06c7aaba9025699a099efc7972d9615fdbdd Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 9 Apr 2019 22:41:36 +0530 Subject: [PATCH 24/53] ios interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/base.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 module_utils/ios/config/base.py diff --git a/module_utils/ios/config/base.py b/module_utils/ios/config/base.py new file mode 100644 index 0000000..1ba31ed --- /dev/null +++ b/module_utils/ios/config/base.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The base class for all ios resource modules +""" + +from ansible.module_utils.connection import Connection + +class ConfigBase(object): #pylint: disable=R0205,R0903 + """ The base class for all ios resource modules + """ + _connection = None + + def __init__(self, module): + self._module = module + self._connection = self._get_connection() + + def _get_connection(self): + if self._connection: + return self._connection + self._connection = Connection(self._module._socket_path) #pylint: disable=W0212 + return self._connection From 126acd96e755826a02526b08e5e193228432ab55 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:01:37 +0530 Subject: [PATCH 25/53] ios interface complete Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/interfaces/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/argspec/interfaces/interfaces.py b/module_utils/ios/argspec/interfaces/interfaces.py index a5c1ea3..3410094 100644 --- a/module_utils/ios/argspec/interfaces/interfaces.py +++ b/module_utils/ios/argspec/interfaces/interfaces.py @@ -41,7 +41,7 @@ def __init__(self, **kwargs): } argument_spec = { - 'state': dict(default='merged', choices=['merged', 'replaced', 'overriden', 'deleted']), + 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), 'config': dict(type='list', elements='dict', options=config_spec) } From fbe658cc45a75f6bdd736e5fa07cb6fcdfc7f12c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:01:46 +0530 Subject: [PATCH 26/53] ios interface complete Signed-off-by: Sumit Jaiswal --- .../ios/config/interfaces/interfaces.py | 162 ++++++++++++------ 1 file changed, 109 insertions(+), 53 deletions(-) diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/ios/config/interfaces/interfaces.py index 6fab1e0..0857452 100644 --- a/module_utils/ios/config/interfaces/interfaces.py +++ b/module_utils/ios/config/interfaces/interfaces.py @@ -14,11 +14,11 @@ from ansible.module_utils.six import iteritems from ansible.module_utils.network.common.utils import to_list -from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs from ansible.module_utils.ios.config.base import ConfigBase from ansible.module_utils.ios.facts.facts import Facts from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list -import q + class Interfaces(ConfigBase, InterfacesArgs): """ @@ -28,6 +28,7 @@ class Interfaces(ConfigBase, InterfacesArgs): gather_subset = [ 'net_configuration_interfaces', ] + params = ('description', 'mtu', 'speed', 'duplex') def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) @@ -48,12 +49,12 @@ def execute_module(self): :returns: The result from moduel execution """ result = {'changed': False} - commands = list() - warnings = list() - - existing_facts = self.get_interface_facts() + commands = [] + warnings = [] + existing_facts = self.get_interfaces_facts() commands.extend(self.set_config(existing_facts)) + if commands: if not self._module.check_mode: self._connection.edit_config(commands) @@ -78,12 +79,12 @@ def set_config(self, existing_facts): to the deisred configuration """ want = self._module.params['config'] - q(want) + for w in want: w.update({'name': normalize_interface(w['name'])}) have = existing_facts#self.get_interfaces_facts() resp = self.set_state(want, have) - q(resp) + return to_list(resp) def set_state(self, want, have): @@ -95,11 +96,11 @@ def set_state(self, want, have): :returns: the commands necessary to migrate the current configuration to the deisred configuration """ - commands = list() - + commands = [] state = self._module.params['state'] + if state == 'overridden': - commands = self._state_overridden(want, have) + commands = Interfaces._state_overridden(want, have) else: for w in want: name = w['name'] @@ -107,13 +108,14 @@ def set_state(self, want, have): obj_in_have = search_obj_in_list(name, have) if state == 'deleted': - commands.extend(self._state_deleted(w, obj_in_have, interface_type)) + commands.extend(Interfaces._state_deleted(w, obj_in_have, interface_type)) if state == 'merged': - commands.extend(self._state_merged(w, obj_in_have, interface_type)) + commands.extend(Interfaces._state_merged(w, obj_in_have)) if state == 'replaced': - commands.extend(self._state_replaced(w, obj_in_have, interface_type)) + commands.extend(Interfaces._state_replaced(w, obj_in_have, interface_type)) + return commands @staticmethod @@ -122,100 +124,154 @@ def _state_replaced(want, obj_in_have, interface_type): :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary + :param interface_type: interface type :rtype: A list :returns: the commands necessary to migrate the current configuration to the deisred configuration """ - commands = list() - if interface_type in ('loopback', 'portchannel', 'svi'): - commands.append('no interface {0}'.format(want['name'])) - commands.extend(Interfaces._state_merged(want, obj_in_have, interface_type)) - else: - commands.append('default interface {0}'.format(want['name'])) - commands.extend(Interfaces._state_merged(want, obj_in_have, interface_type)) - # if want != have: - # # compare and generate command set - # pass + commands = [] + + if want['name']: + interface = 'interface ' + want['name'] + + if obj_in_have: + if interface_type.lower() == 'loopback': + commands.extend(Interfaces._state_merged(want, obj_in_have)) + elif interface_type.lower() == 'gigabitethernet': + for item in Interfaces.params: + value = obj_in_have.get(item) + if value and want[item] != value and value != 'auto': + Interfaces._remove_command_from_interface(interface, item, commands) + commands.extend(Interfaces._state_merged(want, obj_in_have)) return commands @staticmethod - def _state_overridden(want, have): + def _state_overridden(want, obj_in_have): """ The command generator when state is overridden :param want: the desired configuration as a dictionary - :param have: the current configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary :rtype: A list :returns: the commands necessary to migrate the current configuration - to the deisred configuration + to the desired configuration """ - # if want != have: - # pass - commands = list() + commands = [] + interface = 'interface ' + want['name'] - for h in have: - name = h['name'] + for have in obj_in_have: + name = have['name'] obj_in_want = search_obj_in_list(name, want) if not obj_in_want: interface_type = get_interface_type(name) - if interface_type == 'ethernet': + if interface_type.lower() == 'loopback': + Interfaces._remove_command_from_interface(interface, 'description', commands) + elif interface_type.lower() == 'gigabitethernet': default = True - if h['enabled'] is True: - keys = ('description', 'mode', 'mtu', 'speed', 'duplex') - for k, v in iteritems(h): - if k in keys: - if h[k] is not None: + if have['enabled'] is True: + for k, v in iteritems(have): + if k in Interfaces.params: + if have[k] is not None: default = False break else: default = False if default is False: - # Chan interface to its default state - commands.append('default interface {0}'.format(name)) + # Delete the configurable params by interface module + commands.append('no description {0}'.format(name)) + commands.append('no mtu {0}'.format(name)) + commands.append('no speed {0}'.format(name)) + commands.append('no duplex {0}'.format(name)) + + for w in want: + name = w['name'] + obj_in_have = search_obj_in_list(name, obj_in_have) + commands.extend(Interfaces._state_merged(w, obj_in_have)) return commands @staticmethod - def _state_merged(want, have, obj_in_have, interface_type): + def _state_merged(want, obj_in_have): """ The command generator when state is merged :param want: the additive configuration as a dictionary - :param have: the current configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ - commands = [] - - args = ('speed', 'description', 'duplex', 'mtu') name = want['name'] - mode = want.get('mode') + enabled = want.get('enabled') if name: interface = 'interface ' + name - if not obj_in_have: commands.append(interface) - if interface_type in ('ethernet', 'portchannel'): - pass + commands.append('no shutdown') if enabled else commands.append('shutdown') + + for item in Interfaces.params: + candidate = want.get(item) + if candidate: + commands.append(item + ' ' + str(candidate)) + else: + if enabled is True and enabled != obj_in_have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'no shutdown', commands) + elif enabled is False and enabled != obj_in_have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'shutdown', commands) + for item in Interfaces.params: + candidate = want.get(item) + if candidate and candidate != obj_in_have.get(item): + cmd = item + ' ' + str(candidate) + Interfaces._add_command_to_interface(interface, cmd, commands) return commands @staticmethod - def _state_deleted(want, have): + def _state_deleted(want, obj_in_have, interface_type): """ The command generator when state is deleted :param want: the objects from which the configuration should be removed - :param have: the current configuration as a dictionary + :param obj_in_have: the current configuration as a dictionary + :param interface_type: interface type :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ - if want != have: - pass commands = [] + if not obj_in_have or interface_type == 'unknown': + return commands + + interface = 'interface ' + want['name'] + + if 'description' in obj_in_have: + Interfaces._remove_command_from_interface(interface, 'description', commands) + if 'enabled' in obj_in_have and obj_in_have['enabled'] is False: + # if enable is False set enable as True which is the default behavior + Interfaces._remove_command_from_interface(interface, 'shutdown', commands) + + if interface_type.lower() == 'gigabitethernet': + if 'speed' in obj_in_have and obj_in_have['speed'] != 'auto': + Interfaces._remove_command_from_interface(interface, 'speed', commands) + if 'duplex' in obj_in_have and obj_in_have['duplex'] != 'auto': + Interfaces._remove_command_from_interface(interface, 'duplex', commands) + if 'mtu' in obj_in_have: + Interfaces._remove_command_from_interface(interface, 'mtu', commands) + + return commands + + @staticmethod + def _remove_command_from_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) return commands + @staticmethod + def _add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + From 5d7c0d040aae2b4e778fed8905ca785949f62667 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:01:56 +0530 Subject: [PATCH 27/53] ios interface complete Signed-off-by: Sumit Jaiswal --- module_utils/ios/utils/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/module_utils/ios/utils/utils.py b/module_utils/ios/utils/utils.py index 7d4e7ac..d64cad5 100644 --- a/module_utils/ios/utils/utils.py +++ b/module_utils/ios/utils/utils.py @@ -5,6 +5,7 @@ # utils + def search_obj_in_list(name, lst): for o in lst: if o['name'] == name: @@ -32,6 +33,8 @@ def _get_number(name): if_type = 'FastEthernet' elif name.lower().startswith('fo'): if_type = 'FortyGigabitEthernet' + elif name.lower().startswith('long'): + if_type = 'LongReachEthernet' elif name.lower().startswith('et'): if_type = 'Ethernet' elif name.lower().startswith('vl'): @@ -74,6 +77,8 @@ def get_interface_type(interface): return 'FastEthernet' elif interface.upper().startswith('FO'): return 'FortyGigabitEthernet' + elif interface.upper().startswith('LON'): + return 'LongReachEthernet' elif interface.upper().startswith('ET'): return 'Ethernet' elif interface.upper().startswith('VL'): From d9487ee4ae438bb691978a2392dc15de1abdfdbd Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 16 Apr 2019 14:45:43 +0530 Subject: [PATCH 28/53] fix operation idempotency Signed-off-by: Sumit Jaiswal --- .../ios/config/interfaces/interfaces.py | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/ios/config/interfaces/interfaces.py index 0857452..5f0351a 100644 --- a/module_utils/ios/config/interfaces/interfaces.py +++ b/module_utils/ios/config/interfaces/interfaces.py @@ -100,26 +100,27 @@ def set_state(self, want, have): state = self._module.params['state'] if state == 'overridden': - commands = Interfaces._state_overridden(want, have) + kwargs = {'want': want, 'have': have} + commands = Interfaces._state_overridden(**kwargs) else: for w in want: name = w['name'] interface_type = get_interface_type(name) obj_in_have = search_obj_in_list(name, have) - + kwargs = {'want': w, 'have': obj_in_have, 'type': interface_type} if state == 'deleted': - commands.extend(Interfaces._state_deleted(w, obj_in_have, interface_type)) + commands.extend(Interfaces._state_deleted(**kwargs)) if state == 'merged': - commands.extend(Interfaces._state_merged(w, obj_in_have)) + commands.extend(Interfaces._state_merged(**kwargs)) if state == 'replaced': - commands.extend(Interfaces._state_replaced(w, obj_in_have, interface_type)) + commands.extend(Interfaces._state_replaced(**kwargs)) return commands @staticmethod - def _state_replaced(want, obj_in_have, interface_type): + def _state_replaced(**kwargs): """ The command generator when state is replaced :param want: the desired configuration as a dictionary @@ -130,6 +131,9 @@ def _state_replaced(want, obj_in_have, interface_type): to the deisred configuration """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_type = kwargs['type'] if want['name']: interface = 'interface ' + want['name'] @@ -142,12 +146,13 @@ def _state_replaced(want, obj_in_have, interface_type): value = obj_in_have.get(item) if value and want[item] != value and value != 'auto': Interfaces._remove_command_from_interface(interface, item, commands) - commands.extend(Interfaces._state_merged(want, obj_in_have)) + kwargs = {'want': want, 'have': obj_in_have} + commands.extend(Interfaces._state_merged(**kwargs)) return commands @staticmethod - def _state_overridden(want, obj_in_have): + def _state_overridden(**kwargs): """ The command generator when state is overridden :param want: the desired configuration as a dictionary @@ -157,7 +162,9 @@ def _state_overridden(want, obj_in_have): to the desired configuration """ commands = [] - interface = 'interface ' + want['name'] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_want = 'interface ' + want[0]['name'] for have in obj_in_have: name = have['name'] @@ -165,7 +172,8 @@ def _state_overridden(want, obj_in_have): if not obj_in_want: interface_type = get_interface_type(name) if interface_type.lower() == 'loopback': - Interfaces._remove_command_from_interface(interface, 'description', commands) + commands.append('interface ' + name) + commands.append('no description') elif interface_type.lower() == 'gigabitethernet': default = True if have['enabled'] is True: @@ -176,23 +184,40 @@ def _state_overridden(want, obj_in_have): break else: default = False - if default is False: # Delete the configurable params by interface module - commands.append('no description {0}'.format(name)) - commands.append('no mtu {0}'.format(name)) - commands.append('no speed {0}'.format(name)) - commands.append('no duplex {0}'.format(name)) - - for w in want: - name = w['name'] - obj_in_have = search_obj_in_list(name, obj_in_have) - commands.extend(Interfaces._state_merged(w, obj_in_have)) + interface = 'interface ' + name + for each in Interfaces.params: + if interface not in commands: + commands.append(interface) + commands.append('no {0}'.format(each)) + else: + changed = False + # Delete the wanted interface to be replaced with provided values + for k, v in iteritems(have): + if obj_in_want[k] != have[k] and have[k] != "auto": + if interface_want not in commands: + changed = True + commands.append(interface_want) + commands.append('no {0}'.format(k)) + if not changed: + break + + if interface_want in commands: + # if there's change in interface_want then extend the commands + for w in want: + name = w['name'] + have = search_obj_in_list(name, obj_in_have) + kwargs = {'want': w, 'have': have} + commands.extend(Interfaces._state_merged(**kwargs)) + else: + # if there's no change in inteface_want then maintain idempotency + commands = [] return commands @staticmethod - def _state_merged(want, obj_in_have): + def _state_merged(**kwargs): """ The command generator when state is merged :param want: the additive configuration as a dictionary @@ -202,6 +227,8 @@ def _state_merged(want, obj_in_have): the current configuration """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] name = want['name'] enabled = want.get('enabled') @@ -229,7 +256,7 @@ def _state_merged(want, obj_in_have): return commands @staticmethod - def _state_deleted(want, obj_in_have, interface_type): + def _state_deleted(**kwargs): """ The command generator when state is deleted :param want: the objects from which the configuration should be removed @@ -240,6 +267,9 @@ def _state_deleted(want, obj_in_have, interface_type): of the provided objects """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_type = kwargs['type'] if not obj_in_have or interface_type == 'unknown': return commands @@ -263,6 +293,7 @@ def _state_deleted(want, obj_in_have, interface_type): @staticmethod def _remove_command_from_interface(interface, cmd, commands): + #print interface, cmd, commands if interface not in commands: commands.insert(0, interface) commands.append('no %s' % cmd) @@ -273,5 +304,3 @@ def _add_command_to_interface(interface, cmd, commands): if interface not in commands: commands.insert(0, interface) commands.append(cmd) - - From 074d91672b1d6c9d85ebd11a01d4cead1332867a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 16 Apr 2019 14:46:03 +0530 Subject: [PATCH 29/53] pep8 complain fix --- module_utils/ios/utils/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module_utils/ios/utils/utils.py b/module_utils/ios/utils/utils.py index d64cad5..4cffe84 100644 --- a/module_utils/ios/utils/utils.py +++ b/module_utils/ios/utils/utils.py @@ -12,6 +12,7 @@ def search_obj_in_list(name, lst): return o return None + def normalize_interface(name): """Return the normalized interface name """ @@ -65,6 +66,7 @@ def _get_number(name): return proper_interface + def get_interface_type(interface): """Gets the type of interface """ From 3b75917e320c694109b5916e6c5084b8df8ab725 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 23 Apr 2019 17:28:53 +0530 Subject: [PATCH 30/53] idempotency check Signed-off-by: Sumit Jaiswal --- .../ios/config/interfaces/interfaces.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/ios/config/interfaces/interfaces.py index 5f0351a..4f55d6c 100644 --- a/module_utils/ios/config/interfaces/interfaces.py +++ b/module_utils/ios/config/interfaces/interfaces.py @@ -203,15 +203,17 @@ def _state_overridden(**kwargs): if not changed: break - if interface_want in commands: - # if there's change in interface_want then extend the commands - for w in want: - name = w['name'] - have = search_obj_in_list(name, obj_in_have) - kwargs = {'want': w, 'have': have} - commands.extend(Interfaces._state_merged(**kwargs)) - else: - # if there's no change in inteface_want then maintain idempotency + extend = False + for w in want: + name = w['name'] + have = search_obj_in_list(name, obj_in_have) + kwargs = {'want': w, 'have': have} + merge_cmd = Interfaces._state_merged(**kwargs) + if len(merge_cmd) > 0: + # check for the change and extend the cmds + extend = True + commands.extend(merge_cmd) + if not extend: commands = [] return commands From 9406911e7b9dec71641f15e15d2b541e63c03219 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:40:41 +0530 Subject: [PATCH 31/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/library/ios_facts.py b/library/ios_facts.py index ec19228..a332703 100644 --- a/library/ios_facts.py +++ b/library/ios_facts.py @@ -7,9 +7,6 @@ """ from __future__ import absolute_import, division, print_function -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection -from ansible.module_utils.ios.facts.facts import Facts ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': [u'preview'], @@ -47,16 +44,22 @@ # Gather all facts - ios_facts: gather_subset: all - -# Collect only the interfaces and default facts + gather_network_resources: all +# Collect only the ios facts - ios_facts: gather_subset: - - config - -# Do not collect interfaces facts + - !all + - !min + gather_network_resources: + - ios +# Do not collect ios facts - ios_facts: - gather_subset: - - "!hardware" + gather_network_resources: + - "!ios" +# Collect ios and minimal default facts +- ios_facts: + gather_subset: min + gather_network_resources: ios """ RETURN = """ @@ -72,16 +75,23 @@ def main(): """ Main entry point for module execution - :returns: ansible_facts """ module = AnsibleModule(argument_spec=Facts.argument_spec, supports_check_mode=True) - warnings = list() + warnings = ['default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards'] - connection = Connection(module._socket_path) #pylint: disable=W0212 + connection = Connection(module._socket_path) gather_subset = module.params['gather_subset'] - ansible_facts = Facts().get_facts(module, connection, gather_subset) + gather_network_resources = module.params['gather_network_resources'] + result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + + try: + ansible_facts, warning = result + warnings.extend(warning) + except (TypeError, KeyError): + ansible_facts = result + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) if __name__ == '__main__': From 77be86be40918dcd23d2b8274dd51fab8de3d905 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:40:50 +0530 Subject: [PATCH 32/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- library/ios_interfaces.py | 313 +++++++++++++++++++++++++++++--------- 1 file changed, 243 insertions(+), 70 deletions(-) diff --git a/library/ios_interfaces.py b/library/ios_interfaces.py index c8aebff..726e7a9 100644 --- a/library/ios_interfaces.py +++ b/library/ios_interfaces.py @@ -28,64 +28,74 @@ """ from __future__ import absolute_import, division, print_function -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ios.config.interfaces.interfaces import Interfaces +__metaclass__ = type + +GENERATOR_VERSION = '1.0' ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': [u'preview'], - 'supported_by': [u'Ansible Network']} + 'status': ['preview'], + 'supported_by': 'network'} + +NETWORK_OS = "iosxr" +RESOURCE = "l2_interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ --- -module: ios_interfaces -version_added: 2.9 -short_description: Manage Interface on Cisco IOS network devices. -description: Manage Interface on Cisco IOS network devices. -author: [u'Sumit Jaiswal (@justjais)'] -options: - config: - description: The provided configuration - suboptions: - name: + module: ios_interfaces + version_added: 2.9 + short_description: Manages interface attributes of Cisco IOS network devices + description: This module manages the interface attributes of Cisco IOS network devices. + author: Sumit Jaiswal (@justjais) + options: + config: + description: A dictionary of interface options + type: list + elements: dict + suboptions: + name: + description: + - Full name of interface, e.g. GigabitEthernet0/2, loopback999. + type: str + required: True description: - - Name of interface, i.e. GigabitEthernet0/2, loopback999. - type: str - required: true + description: + - Interface description. + type: str + enable: + description: + - Administrative state of the interface. + - Set the value to C(true) to administratively enable the interface or C(false) to disable it. + type: bool + default: true + speed: + description: + - Interface link speed. Applicable for Ethernet interfaces only. + type: str + mtu: + description: + - MTU for a specific interface. Must be a number between 64 and 9600. + Applicable for Ethernet interfaces only. + type: str + duplex: + default: automatic + description: + - Interface link status. Applicable for Ethernet interfaces only, either in half duplex, + full duplex or in automatic state which negotiates the duplex automatically. + type: str + default: auto + choices: ['full', 'half', 'auto'] + state: + choices: + - merged + - replaced + - overridden + - deleted + default: merged description: - description: - - Description of Interface. - type: str - enabled: - description: - - Interface link status. - type: bool - default: true - speed: - description: - - Interface link speed. Applicable for ethernet interface only. - type: str - mtu: - description: - - Maximum size of transmit packet. Must be an number between 64 and 9600. - Applicable for Ethernet interface only. - type: str - duplex: - description: - - Interface link status. Applicable for ethernet interface only. - type: str - default: auto - choices: ['full', 'half', 'auto'] - state: - choices: - - merged - - replaced - - overridden - - deleted - default: merged - description: - - The state the configuration should be left in - type: str + - The state the configuration should be left in + type: str """ EXAMPLES = """ @@ -93,60 +103,220 @@ # Using merged -- name: Configure Ethernet interfaces +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# no ip address +# duplex auto +# speed auto + +- name: Merge provided configuration with device configuration ios_interfaces: config: - name: GigabitEthernet0/2 - description: 'Configured by Ansible' + description: 'Configured and Merged by Ansible Network' enabled: True - name: GigabitEthernet0/3 - description: 'Configured by Ansible Network' + description: 'Configured and Merged by Ansible Network' + mtu: 2800 enabled: False + speed: 100 duplex: full operation: merged +# After state: +# ------------ +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured and Merged by Ansible Network +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured and Merged by Ansible Network +# mtu 2800 +# no ip address +# shutdown +# duplex full +# speed 100 + # Using replaced -- name: Configure following interfaces and replace their existing config +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured by Ansible Network +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# mtu 2000 +# no ip address +# shutdown +# duplex full +# speed 100 + +- name: Replaces device configuration of listed interfaces with provided configuration ios_interfaces: config: - - name: GigabitEthernet0/2 - description: Configured by Ansible - enabled: True - mtu: 2000 - name: GigabitEthernet0/3 - description: 'Configured by Ansible Network' + description: 'Configured and Replaced by Ansible Network' enabled: False duplex: auto + mtu: 2500 + speed: 1000 operation: replaced +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured by Ansible Network +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured and Replaced by Ansible Network +# mtu 2500 +# no ip address +# shutdown +# duplex full +# speed 1000 + # Using overridden -- name: Override interfaces +# Before state: +# ------------- +# +# vios#show running-config | section ^interface# +# interface GigabitEthernet0/1 +# description Configured by Ansible +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description This is test +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured by Ansible +# mtu 2800 +# no ip address +# shutdown +# duplex full +# speed 100 + +- name: Override device configuration of all interfaces with provided configuration ios_interfaces: config: - name: GigabitEthernet0/2 - description: 'Configured by Ansible' - enabled: True - duplex: auto + description: 'Configured and Overridden by Ansible Network' + speed: 1000 - name: GigabitEthernet0/3 - description: 'Configured by Ansible Network' + description: 'Configured and Overridden by Ansible Network' enabled: False - speed: 1000 + duplex: full + mtu: 2000 operation: overridden -# Using deleted +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured and Overridden by Ansible Network +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured and Overridden by Ansible Network +# mtu 2000 +# no ip address +# shutdown +# duplex full +# speed 100 + +# Using Deleted -- name: Delete IOS interfaces as in given arguments +# Before state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# description Configured and Overridden by Ansible Network +# no ip address +# duplex auto +# speed 1000 +# interface GigabitEthernet0/3 +# description Configured and Replaced by Ansible Network +# mtu 2500 +# no ip address +# shutdown +# duplex full +# speed 1000 + +- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) ios_interfaces: config: - name: GigabitEthernet0/2 - description: 'Configured by Ansible' - name: GigabitEthernet0/3 - description: 'Configured by Ansible Network' - mtu: 1800 operation: deleted +# After state: +# ------------- +# +# vios#show running-config | section ^interface +# interface GigabitEthernet0/1 +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/2 +# no ip address +# duplex auto +# speed auto +# interface GigabitEthernet0/3 +# no ip address +# duplex auto +# speed auto + """ RETURN = """ @@ -165,6 +335,9 @@ sample: ['command 1', 'command 2', 'command 3'] """ +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ios.config.interfaces.interfaces import Interfaces + def main(): """ From 0c6250fc8681fa3e27162a3cbe375d5eef469a5f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:41:00 +0530 Subject: [PATCH 33/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/facts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py index 5a3a74f..0c4ae32 100644 --- a/module_utils/ios/argspec/facts/facts.py +++ b/module_utils/ios/argspec/facts/facts.py @@ -1,21 +1,22 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. +# {{ rm['COPYRIGHT'] }} # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The arg spec for the ios facts module. +The arg spec for the iosxr facts module. """ -class FactsArgs(object): +class FactsArgs(object): #pylint: disable=R0903 + """ The arg spec for the ios facts module + """ def __init__(self, **kwargs): pass choices = [ 'all', - 'net_configuration_interfaces', + 'interfaces', ] - argument_spec = { - 'gather_subset': dict(default=['all'], choices=choices, type='list') + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(default=['all'], choices=choices, type='list'), } - From 2da81e1c9cad01aa90c038d8898f352419cbd039 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:41:09 +0530 Subject: [PATCH 34/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- .../ios/config/interfaces/interfaces.py | 266 +++++++++--------- 1 file changed, 131 insertions(+), 135 deletions(-) diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/ios/config/interfaces/interfaces.py index 4f55d6c..f3c46b5 100644 --- a/module_utils/ios/config/interfaces/interfaces.py +++ b/module_utils/ios/config/interfaces/interfaces.py @@ -11,7 +11,6 @@ """ -from ansible.module_utils.six import iteritems from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs @@ -26,8 +25,14 @@ class Interfaces(ConfigBase, InterfacesArgs): """ gather_subset = [ - 'net_configuration_interfaces', + '!all', + '!min', ] + + gather_network_resources = [ + 'interfaces', + ] + params = ('description', 'mtu', 'speed', 'duplex') def get_interfaces_facts(self): @@ -36,10 +41,12 @@ def get_interfaces_facts(self): :rtype: A dictionary :returns: The current configuration as a dictionary """ - facts = Facts().get_facts(self._module, self._connection, self.gather_subset) - interfaces_facts = facts['net_configuration'].get('interfaces') + result = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) + facts = result + interfaces_facts = facts['ansible_network_resources'].get('interfaces') if not interfaces_facts: return [] + return interfaces_facts def execute_module(self): @@ -49,8 +56,8 @@ def execute_module(self): :returns: The result from moduel execution """ result = {'changed': False} - commands = [] - warnings = [] + commands = list() + warnings = list() existing_facts = self.get_interfaces_facts() commands.extend(self.set_config(existing_facts)) @@ -63,11 +70,11 @@ def execute_module(self): interfaces_facts = self.get_interfaces_facts() - result['before'] = interfaces_facts + result['before'] = existing_facts if result['changed']: result['after'] = interfaces_facts - result['warnings'] = warnings + return result def set_config(self, existing_facts): @@ -82,7 +89,7 @@ def set_config(self, existing_facts): for w in want: w.update({'name': normalize_interface(w['name'])}) - have = existing_facts#self.get_interfaces_facts() + have = existing_facts resp = self.set_state(want, have) return to_list(resp) @@ -101,21 +108,16 @@ def set_state(self, want, have): if state == 'overridden': kwargs = {'want': want, 'have': have} - commands = Interfaces._state_overridden(**kwargs) - else: - for w in want: - name = w['name'] - interface_type = get_interface_type(name) - obj_in_have = search_obj_in_list(name, have) - kwargs = {'want': w, 'have': obj_in_have, 'type': interface_type} - if state == 'deleted': - commands.extend(Interfaces._state_deleted(**kwargs)) - - if state == 'merged': - commands.extend(Interfaces._state_merged(**kwargs)) - - if state == 'replaced': - commands.extend(Interfaces._state_replaced(**kwargs)) + commands = self._state_overridden(**kwargs) + elif state == 'deleted': + kwargs = {'want': want, 'have': have} + commands = self._state_deleted(**kwargs) + elif state == 'merged': + kwargs = {'want': want, 'have': have} + commands = self._state_merged(**kwargs) + elif state == 'replaced': + kwargs = {'want': want, 'have': have} + commands = self._state_replaced(**kwargs) return commands @@ -132,22 +134,20 @@ def _state_replaced(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_type = kwargs['type'] - - if want['name']: - interface = 'interface ' + want['name'] - - if obj_in_have: - if interface_type.lower() == 'loopback': - commands.extend(Interfaces._state_merged(want, obj_in_have)) - elif interface_type.lower() == 'gigabitethernet': - for item in Interfaces.params: - value = obj_in_have.get(item) - if value and want[item] != value and value != 'auto': - Interfaces._remove_command_from_interface(interface, item, commands) - kwargs = {'want': want, 'have': obj_in_have} - commands.extend(Interfaces._state_merged(**kwargs)) + have = kwargs['have'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each, } + commands.extend(Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands} + commands.extend(Interfaces.set_interface(**kwargs)) return commands @@ -163,58 +163,25 @@ def _state_overridden(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_want = 'interface ' + want[0]['name'] - - for have in obj_in_have: - name = have['name'] - obj_in_want = search_obj_in_list(name, want) - if not obj_in_want: - interface_type = get_interface_type(name) - if interface_type.lower() == 'loopback': - commands.append('interface ' + name) - commands.append('no description') - elif interface_type.lower() == 'gigabitethernet': - default = True - if have['enabled'] is True: - for k, v in iteritems(have): - if k in Interfaces.params: - if have[k] is not None: - default = False - break - else: - default = False - if default is False: - # Delete the configurable params by interface module - interface = 'interface ' + name - for each in Interfaces.params: - if interface not in commands: - commands.append(interface) - commands.append('no {0}'.format(each)) - else: - changed = False - # Delete the wanted interface to be replaced with provided values - for k, v in iteritems(have): - if obj_in_want[k] != have[k] and have[k] != "auto": - if interface_want not in commands: - changed = True - commands.append(interface_want) - commands.append('no {0}'.format(k)) - if not changed: - break + have = kwargs['have'] - extend = False - for w in want: - name = w['name'] - have = search_obj_in_list(name, obj_in_have) - kwargs = {'want': w, 'have': have} - merge_cmd = Interfaces._state_merged(**kwargs) - if len(merge_cmd) > 0: - # check for the change and extend the cmds - extend = True - commands.extend(merge_cmd) - if not extend: - commands = [] + for each in have: + for interface in want: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + # We didn't find a matching desired state, which means we can + # pretend we recieved an empty desired state. + interface = dict(name=each['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands} + commands.extend(Interfaces.set_interface(**kwargs)) return commands @@ -230,30 +197,16 @@ def _state_merged(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - name = want['name'] - enabled = want.get('enabled') - - if name: - interface = 'interface ' + name - if not obj_in_have: - commands.append(interface) - commands.append('no shutdown') if enabled else commands.append('shutdown') - - for item in Interfaces.params: - candidate = want.get(item) - if candidate: - commands.append(item + ' ' + str(candidate)) - else: - if enabled is True and enabled != obj_in_have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'no shutdown', commands) - elif enabled is False and enabled != obj_in_have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'shutdown', commands) - for item in Interfaces.params: - candidate = want.get(item) - if candidate and candidate != obj_in_have.get(item): - cmd = item + ' ' + str(candidate) - Interfaces._add_command_to_interface(interface, cmd, commands) + have = kwargs['have'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.set_interface(**kwargs)) return commands @@ -270,32 +223,23 @@ def _state_deleted(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_type = kwargs['type'] - if not obj_in_have or interface_type == 'unknown': - return commands + have = kwargs['have'] - interface = 'interface ' + want['name'] - - if 'description' in obj_in_have: - Interfaces._remove_command_from_interface(interface, 'description', commands) - if 'enabled' in obj_in_have and obj_in_have['enabled'] is False: - # if enable is False set enable as True which is the default behavior - Interfaces._remove_command_from_interface(interface, 'shutdown', commands) - - if interface_type.lower() == 'gigabitethernet': - if 'speed' in obj_in_have and obj_in_have['speed'] != 'auto': - Interfaces._remove_command_from_interface(interface, 'speed', commands) - if 'duplex' in obj_in_have and obj_in_have['duplex'] != 'auto': - Interfaces._remove_command_from_interface(interface, 'duplex', commands) - if 'mtu' in obj_in_have: - Interfaces._remove_command_from_interface(interface, 'mtu', commands) + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) return commands @staticmethod def _remove_command_from_interface(interface, cmd, commands): - #print interface, cmd, commands + # To delete the passed config if interface not in commands: commands.insert(0, interface) commands.append('no %s' % cmd) @@ -303,6 +247,58 @@ def _remove_command_from_interface(interface, cmd, commands): @staticmethod def _add_command_to_interface(interface, cmd, commands): + # To set the passed config if interface not in commands: commands.insert(0, interface) commands.append(cmd) + + @staticmethod + def set_interface(**kwargs): + # Set the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + clear_cmds = [] + + if kwargs.get('commands'): + clear_cmds = kwargs['commands'] + + interface = 'interface ' + want['name'] + + if want.get('enabled') and want.get('enabled') != have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'no shutdown', commands) + elif not want.get('enabled') and want.get('enabled') != have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'shutdown', commands) + for item in Interfaces.params: + cmd = 'no ' + item + candidate = want.get(item) + if candidate and (candidate != have.get(item) or cmd in clear_cmds): + cmd = item + ' ' + str(candidate) + Interfaces._add_command_to_interface(interface, cmd, commands) + + return commands + + @staticmethod + def clear_interface(**kwargs): + # Delete the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + interface_type = get_interface_type(want['name']) + interface = 'interface ' + want['name'] + + if have.get('description') and want.get('description') != have.get('description'): + Interfaces._remove_command_from_interface(interface, 'description', commands) + if not have.get('enabled') and want.get('enabled') != have.get('enabled'): + # if enable is False set enable as True which is the default behavior + Interfaces._remove_command_from_interface(interface, 'shutdown', commands) + + if interface_type.lower() == 'gigabitethernet': + if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'): + Interfaces._remove_command_from_interface(interface, 'speed', commands) + if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'): + Interfaces._remove_command_from_interface(interface, 'duplex', commands) + if have.get('mtu') and want.get('mtu') != have.get('mtu'): + Interfaces._remove_command_from_interface(interface, 'mtu', commands) + + return commands \ No newline at end of file From f8b5ebacf30b2f7120f8b0b941bfaebaca40d761 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:41:19 +0530 Subject: [PATCH 35/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/facts/base.py b/module_utils/ios/facts/base.py index 913df74..acdd38f 100644 --- a/module_utils/ios/facts/base.py +++ b/module_utils/ios/facts/base.py @@ -17,7 +17,7 @@ class FactsBase(object): The ios facts base class """ generated_spec = {} - ansible_facts = {'net_configuration': {}} + ansible_facts = {'ansible_network_resources': {}} def __init__(self, argspec, subspec=None, options=None): spec = deepcopy(argspec) From 072cf25324798461a3b0daff14c071b03fd1b758 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:41:28 +0530 Subject: [PATCH 36/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/facts.py | 90 ++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/module_utils/ios/facts/facts.py b/module_utils/ios/facts/facts.py index 1df0a0b..fb2aeb4 100644 --- a/module_utils/ios/facts/facts.py +++ b/module_utils/ios/facts/facts.py @@ -8,40 +8,41 @@ calls the appropriate facts gathering function """ +from ansible.module_utils.six import iteritems + from ansible.module_utils.ios.argspec.facts.facts import FactsArgs from ansible.module_utils.ios.facts.base import FactsBase from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs from ansible.module_utils.ios.facts.interfaces.interfaces import InterfacesFacts -class Facts(FactsArgs, FactsBase): +FACT_SUBSETS = {} + +class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 """ The fact class for ios """ - def get_facts(self, module, connection, gather_subset=['all']): - """ Collect the facts for ios - - :param module: The module instance - :param connection: The device connection - :param gather_subset: The facts subset to collect - :rtype: dict - :returns: the facts gathered - """ - valid_subsets = self.argument_spec['gather_subset'].get('choices', []) - if valid_subsets and 'all' in valid_subsets: - valid_subsets.remove('all') + VALID_GATHER_SUBSETS = frozenset(FACT_SUBSETS.keys()) + def generate_runable_subsets(self, module, subsets, valid_subsets): runable_subsets = set() exclude_subsets = set() + minimal_gather_subset = frozenset(['default']) - for subset in gather_subset: + for subset in subsets: if subset == 'all': runable_subsets.update(valid_subsets) continue + if subset == 'min' and minimal_gather_subset: + runable_subsets.update(minimal_gather_subset) + continue if subset.startswith('!'): subset = subset[1:] + if subset == 'min': + exclude_subsets.update(minimal_gather_subset) + continue if subset == 'all': - exclude_subsets.update(valid_subsets) + exclude_subsets.update(valid_subsets - minimal_gather_subset) continue exclude = True else: @@ -57,16 +58,59 @@ def get_facts(self, module, connection, gather_subset=['all']): if not runable_subsets: runable_subsets.update(valid_subsets) - runable_subsets.difference_update(exclude_subsets) - self.ansible_facts['gather_subset'] = list(runable_subsets) - for attr in runable_subsets: - getattr(self, '_get_%s' % attr, {})(module, connection) + return runable_subsets - return self.ansible_facts + def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): + """ Collect the facts for iosxr + :param module: The module instance + :param connection: The device connection + :param gather_subset: The facts subset to collect + :param gather_network_resources: The resource subset to collect + :rtype: dict + :returns: the facts gathered + """ + warnings = [] + self.ansible_facts['gather_network_resources'] = list() + self.ansible_facts['gather_subset'] = list() - @staticmethod - def _get_net_configuration_interfaces(module, connection): - return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) + valid_network_resources_subsets = self.argument_spec['gather_network_resources'].get('choices', []) + if valid_network_resources_subsets and 'all' in valid_network_resources_subsets: + valid_network_resources_subsets.remove('all') + + if valid_network_resources_subsets: + resources_runable_subsets = self.generate_runable_subsets(module, gather_network_resources, valid_network_resources_subsets) + if resources_runable_subsets: + self.ansible_facts['gather_network_resources'] = list(resources_runable_subsets) + for attr in resources_runable_subsets: + getattr(self, '_get_%s' % attr, {})(module, connection) + if self.VALID_GATHER_SUBSETS: + runable_subsets = self.generate_runable_subsets(module, gather_subset, self.VALID_GATHER_SUBSETS) + + if runable_subsets: + facts = dict() + self.ansible_facts['gather_subset'] = list(runable_subsets) + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + warnings.extend(inst.warnings) + + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + self.ansible_facts[key] = value + + if warnings: + return self.ansible_facts, warnings + else: + return self.ansible_facts + + + @staticmethod + def _get_interfaces(module, connection): + return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) \ No newline at end of file From f64afb3d87c034519271cce97953f3622ae87251 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:41:38 +0530 Subject: [PATCH 37/53] adhered to new facts Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/interfaces/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/facts/interfaces/interfaces.py b/module_utils/ios/facts/interfaces/interfaces.py index 4667eef..88cea5e 100644 --- a/module_utils/ios/facts/interfaces/interfaces.py +++ b/module_utils/ios/facts/interfaces/interfaces.py @@ -44,7 +44,7 @@ def populate_facts(self, module, connection, data=None): if objs: facts['interfaces'] = objs - self.ansible_facts['net_configuration'].update(facts) + self.ansible_facts['ansible_network_resources'].update(facts) return self.ansible_facts def render_config(self, spec, conf): From cb51f155f90467991ca25feead535f32e01dbb7a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:44:44 +0530 Subject: [PATCH 38/53] cosmetic change Signed-off-by: Sumit Jaiswal --- library/ios_facts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ios_facts.py b/library/ios_facts.py index a332703..42e56ac 100644 --- a/library/ios_facts.py +++ b/library/ios_facts.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The module file for ios_facts From 42634ef88fc78f18b4de2fb46524956aa6032605 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:45:00 +0530 Subject: [PATCH 39/53] cosmetic change Signed-off-by: Sumit Jaiswal --- library/ios_interfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ios_interfaces.py b/library/ios_interfaces.py index 726e7a9..d8ac399 100644 --- a/library/ios_interfaces.py +++ b/library/ios_interfaces.py @@ -36,8 +36,8 @@ 'status': ['preview'], 'supported_by': 'network'} -NETWORK_OS = "iosxr" -RESOURCE = "l2_interfaces" +NETWORK_OS = "ios" +RESOURCE = "interfaces" COPYRIGHT = "Copyright 2019 Red Hat" From f58a72a38e993e4df430481e176fb0b77deaeefa Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:45:11 +0530 Subject: [PATCH 40/53] cosmetic change Signed-off-by: Sumit Jaiswal --- module_utils/ios/facts/facts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/facts/facts.py b/module_utils/ios/facts/facts.py index fb2aeb4..d89ad02 100644 --- a/module_utils/ios/facts/facts.py +++ b/module_utils/ios/facts/facts.py @@ -63,7 +63,7 @@ def generate_runable_subsets(self, module, subsets, valid_subsets): return runable_subsets def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): - """ Collect the facts for iosxr + """ Collect the facts for ios :param module: The module instance :param connection: The device connection :param gather_subset: The facts subset to collect From 77cefb86ad2a2f62a2e2f638e0be47fb44cefee5 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:45:24 +0530 Subject: [PATCH 41/53] cosmetic change Signed-off-by: Sumit Jaiswal --- module_utils/ios/config/interfaces/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/ios/config/interfaces/interfaces.py index f3c46b5..99e5d02 100644 --- a/module_utils/ios/config/interfaces/interfaces.py +++ b/module_utils/ios/config/interfaces/interfaces.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The ios_interfaces class From 0c355e3d508686a778ba347cfb1f40d953b117c5 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:46:08 +0530 Subject: [PATCH 42/53] cosmetic change Signed-off-by: Sumit Jaiswal --- module_utils/ios/argspec/facts/facts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py index 0c4ae32..9ed0376 100644 --- a/module_utils/ios/argspec/facts/facts.py +++ b/module_utils/ios/argspec/facts/facts.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# {{ rm['COPYRIGHT'] }} +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The arg spec for the iosxr facts module. +The arg spec for the ios facts module. """ class FactsArgs(object): #pylint: disable=R0903 From 747f1bd3aeaf7655556368aeac158bc2511d8f5c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 12:46:15 +0530 Subject: [PATCH 43/53] cosmetic change Signed-off-by: Sumit Jaiswal --- module_utils/network/argspec/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/network/argspec/base.py b/module_utils/network/argspec/base.py index 596bbab..89fc67a 100644 --- a/module_utils/network/argspec/base.py +++ b/module_utils/network/argspec/base.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The argspec base class """ From 5a4d60c5022268f434391d947fd0fdafd3fe2dda Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:29:25 +0530 Subject: [PATCH 44/53] ios interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/deleted.yaml | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/modules/interfaces/deleted.yaml diff --git a/tests/modules/interfaces/deleted.yaml b/tests/modules/interfaces/deleted.yaml new file mode 100644 index 0000000..aab8b70 --- /dev/null +++ b/tests/modules/interfaces/deleted.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: "START ios_interfaces Deleted integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) + ios_interfaces: + config: + - name: GigabitEthernet0/1 + - name: GigabitEthernet0/2 + state: deleted + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + duplex: auto + enable: True + - name: GigabitEthernet0/2 + duplex: auto + enable: True + - name: GigabitEthernet0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From ceb4421ed0e927835d18e1b20dc0b0ca5be5ed57 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:29:25 +0530 Subject: [PATCH 45/53] ios interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/main.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/modules/interfaces/main.yaml diff --git a/tests/modules/interfaces/main.yaml b/tests/modules/interfaces/main.yaml new file mode 100644 index 0000000..ecfb757 --- /dev/null +++ b/tests/modules/interfaces/main.yaml @@ -0,0 +1,15 @@ +--- +- hosts: ios + gather_subset: + - net_configuration_interfaces + + tasks: + - import_role: + name: ./network + + - include_tasks: "{{ item }}.yaml" + loop: + - merged + - replaced + - overridden + - deleted From 541e13243dacbb3589dc3a485c36b895ba8d9560 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:29:25 +0530 Subject: [PATCH 46/53] ios interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/merged.yaml | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/modules/interfaces/merged.yaml diff --git a/tests/modules/interfaces/merged.yaml b/tests/modules/interfaces/merged.yaml new file mode 100644 index 0000000..45be69a --- /dev/null +++ b/tests/modules/interfaces/merged.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: "START ios_interfaces Merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Merge provided configuration with device configuration + ios_interfaces: + config: + - name: GigabitEthernet0/1 + enable: True + - name: GigabitEthernet0/2 + description: 'Configured by Ansible-Network' + speed: 10 + duplex: half + enable: False + state: merged + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + description: Interface 1 + speed: 100 + duplex: auto + enable: True + - name: GigabitEthernet0/2 + description: 'Configured by Ansible-Network' + speed: 10 + duplex: half + enable: False + mtu: 1000 + - name: GigabitEthernet0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From 42a4253cf8df04f811b16b53a4fd47501dc8419b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:29:25 +0530 Subject: [PATCH 47/53] ios interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/overridden.yaml | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/modules/interfaces/overridden.yaml diff --git a/tests/modules/interfaces/overridden.yaml b/tests/modules/interfaces/overridden.yaml new file mode 100644 index 0000000..de3ca54 --- /dev/null +++ b/tests/modules/interfaces/overridden.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: "START ios_interfaces Overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Override device configuration of all interfaces with provided configuration + ios_interfaces: + config: + - name: GigabitEthernet0/1 + enable: True + - name: GigabitEthernet0/2 + description: 'Configured by Ansible-Network' + enable: False + state: overridden + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + duplex: auto + enable: True + - name: GigabitEthernet0/2 + description: 'Configured by Ansible-Network' + duplex: auto + enable: False + - name: GigabitEthernet0/3 + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From fcbda7b730169757dc0519e3f9274cc656376150 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:29:25 +0530 Subject: [PATCH 48/53] ios interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/replaced.yaml | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/modules/interfaces/replaced.yaml diff --git a/tests/modules/interfaces/replaced.yaml b/tests/modules/interfaces/replaced.yaml new file mode 100644 index 0000000..312e7ad --- /dev/null +++ b/tests/modules/interfaces/replaced.yaml @@ -0,0 +1,38 @@ +--- +- debug: + msg: "START ios_interfaces Replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Replaces device configuration of listed interfaces with provided configuration + ios_interfaces: + config: + - name: GigabitEthernet0/1 + enable: True + - name: GigabitEthernet0/2 + description: 'Configured by Ansible-Network' + enable: False + state: replaced + +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + duplex: auto + enable: True + - name: GigabitEthernet0/2 + description: 'Configured by Ansible-Network' + duplex: auto + enable: False + - name: GigabitEthernet0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From 52e277fb6d4db624460b9a4550b3fe73b82ad99c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:29:25 +0530 Subject: [PATCH 49/53] ios interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/reset_config.yaml | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/modules/interfaces/reset_config.yaml diff --git a/tests/modules/interfaces/reset_config.yaml b/tests/modules/interfaces/reset_config.yaml new file mode 100644 index 0000000..937ee19 --- /dev/null +++ b/tests/modules/interfaces/reset_config.yaml @@ -0,0 +1,39 @@ +--- +- name: Reset initial config + cli_config: + config: | + interface GigabitEthernet0/1 + description Ansible-Network configured + shutdown + duplex auto + speed 100 + interface GigabitEthernet0/2 + duplex auto + speed auto + mtu 1000 + interface GigabitEthernet0/3 + description Ansible-Network configured + duplex auto + speed auto +- ios_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/1 + description: 'Ansible-Network configured' + speed: 100 + duplex: auto + enable: False + - name: GigabitEthernet0/2 + duplex: auto + enable: True + mtu: 1000 + - name: GigabitEthernet0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" From 7fd62d4a82108b044ba0331a2ee5a9175b4c52ed Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 29 Jul 2019 17:41:30 +0530 Subject: [PATCH 50/53] updated interfaces resource --- library/ios_facts.py | 79 ++++++------ library/ios_interfaces.py | 57 +++++---- module_utils/ios/argspec/facts/facts.py | 22 ---- .../ios/argspec/interfaces/interfaces.py | 47 ------- module_utils/ios/config/base.py | 24 ---- module_utils/ios/facts/base.py | 110 ----------------- module_utils/ios/facts/facts.py | 116 ------------------ .../ios/facts/interfaces/interfaces.py | 74 ----------- module_utils/network/argspec/__init__.py | 0 module_utils/network/argspec/base.py | 12 -- module_utils/{ => network}/ios/__init__.py | 0 .../{ => network}/ios/argspec/__init__.py | 0 .../ios/argspec/facts/__init__.py | 0 .../network/ios/argspec/facts/facts.py | 28 +++++ .../ios/argspec/interfaces/__init__.py | 0 .../ios/argspec/interfaces/interfaces.py | 44 +++++++ .../{ => network}/ios/config/__init__.py | 0 .../ios/config/interfaces/__init__.py | 0 .../ios/config/interfaces/interfaces.py | 107 ++++++++-------- .../{ => network}/ios/facts/__init__.py | 0 module_utils/network/ios/facts/facts.py | 48 ++++++++ .../ios/facts/interfaces/__init__.py | 0 .../ios/facts/interfaces/interfaces.py | 94 ++++++++++++++ .../{ => network}/ios/utils/__init__.py | 0 module_utils/{ => network}/ios/utils/utils.py | 7 +- 25 files changed, 344 insertions(+), 525 deletions(-) delete mode 100644 module_utils/ios/argspec/facts/facts.py delete mode 100644 module_utils/ios/argspec/interfaces/interfaces.py delete mode 100644 module_utils/ios/config/base.py delete mode 100644 module_utils/ios/facts/base.py delete mode 100644 module_utils/ios/facts/facts.py delete mode 100644 module_utils/ios/facts/interfaces/interfaces.py delete mode 100644 module_utils/network/argspec/__init__.py delete mode 100644 module_utils/network/argspec/base.py rename module_utils/{ => network}/ios/__init__.py (100%) rename module_utils/{ => network}/ios/argspec/__init__.py (100%) rename module_utils/{ => network}/ios/argspec/facts/__init__.py (100%) create mode 100644 module_utils/network/ios/argspec/facts/facts.py rename module_utils/{ => network}/ios/argspec/interfaces/__init__.py (100%) create mode 100644 module_utils/network/ios/argspec/interfaces/interfaces.py rename module_utils/{ => network}/ios/config/__init__.py (100%) rename module_utils/{ => network}/ios/config/interfaces/__init__.py (100%) rename module_utils/{ => network}/ios/config/interfaces/interfaces.py (77%) rename module_utils/{ => network}/ios/facts/__init__.py (100%) create mode 100644 module_utils/network/ios/facts/facts.py rename module_utils/{ => network}/ios/facts/interfaces/__init__.py (100%) create mode 100644 module_utils/network/ios/facts/interfaces/interfaces.py rename module_utils/{ => network}/ios/utils/__init__.py (100%) rename module_utils/{ => network}/ios/utils/utils.py (94%) diff --git a/library/ios_facts.py b/library/ios_facts.py index 42e56ac..578ec5f 100644 --- a/library/ios_facts.py +++ b/library/ios_facts.py @@ -1,43 +1,57 @@ -#!/usr/bin/python +# # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The module file for ios_facts """ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': [u'preview'], - 'supported_by': [u'Ansible Network']} + 'supported_by': 'network'} DOCUMENTATION = """ --- module: ios_facts version_added: 2.9 -short_description: Collect facts from remote devices running Cisco IOS +short_description: Get facts about Cisco ios devices. description: - - Collects a base set of device facts from a remote device that - is running IOS. This module prepends all of the - base network fact keys with C(ansible_net_). The facts - module will always collect a base set of facts from the device - and can enable or disable collection of additional facts. + - Collects facts from network devices running the ios operating + system. This module places the facts gathered in the fact tree keyed by the + respective resource name. The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. author: [u'Sumit Jaiswal (@justjais)'] notes: - - Tested against IOS Version 15.6(3)M2 on VIRL + - Tested against Cisco IOSv Version 15.2 on VIRL options: gather_subset: description: - - When supplied, this argument restricts the facts collected - to a given subset. - - Possible values for this argument include - C(all), C(hardware), C(config), and C(interfaces). - - Specify a list of values to include a larger subset. - - Use a value with an initial C(!) to collect all facts except that subset. + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, min, hardware, config, legacy, and lacp_interfaces. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + required: false + default: 'all' + version_added: "2.2" + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all and the resources like lacp_interfaces, vlans etc. + Can specify a list of values to include a larger subset. Values + can also be used with an initial C(M(!)) to specify that a + specific subset should not be collected. required: false - default: '!config' + version_added: "2.9" """ EXAMPLES = """ @@ -51,25 +65,24 @@ - !all - !min gather_network_resources: - - ios + - interfaces # Do not collect ios facts - ios_facts: gather_network_resources: - - "!ios" + - "!interfaces" # Collect ios and minimal default facts - ios_facts: gather_subset: min - gather_network_resources: ios + gather_network_resources: interfaces """ RETURN = """ See the respective resource module parameters for the tree. """ - from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection -from ansible.module_utils.ios.facts.facts import Facts +from ansible.module_utils.network.ios.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.ios.facts.facts import Facts def main(): @@ -77,22 +90,18 @@ def main(): Main entry point for module execution :returns: ansible_facts """ - module = AnsibleModule(argument_spec=Facts.argument_spec, + module = AnsibleModule(argument_spec=FactsArgs.argument_spec, supports_check_mode=True) - warnings = ['default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards'] + warnings = ['default value for `gather_subset` ' + 'will be changed to `min` from `!config` v2.11 onwards'] - connection = Connection(module._socket_path) - gather_subset = module.params['gather_subset'] - gather_network_resources = module.params['gather_network_resources'] - result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + result = Facts(module).get_facts() - try: - ansible_facts, warning = result - warnings.extend(warning) - except (TypeError, KeyError): - ansible_facts = result + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + if __name__ == '__main__': main() diff --git a/library/ios_interfaces.py b/library/ios_interfaces.py index d8ac399..536db1b 100644 --- a/library/ios_interfaces.py +++ b/library/ios_interfaces.py @@ -1,27 +1,26 @@ -#!/usr/bin/python +# # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -############################################## -################# WARNING #################### -############################################## -### -### This file is auto generated by the resource -### module builder playbook. -### -### Do not edit this file manually. -### -### Changes to this file will be over written -### by the resource module builder. -### -### Changes should be made in the model used to -### generate this file or in the resource module -### builder template. -### -############################################## -############################################## -############################################## +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# """ The module file for ios_interfaces @@ -48,6 +47,8 @@ short_description: Manages interface attributes of Cisco IOS network devices description: This module manages the interface attributes of Cisco IOS network devices. author: Sumit Jaiswal (@justjais) + notes: + - Tested against Cisco IOSv Version 15.2 on VIRL options: config: description: A dictionary of interface options @@ -63,7 +64,7 @@ description: - Interface description. type: str - enable: + enabled: description: - Administrative state of the interface. - Set the value to C(true) to administratively enable the interface or C(false) to disable it. @@ -332,11 +333,12 @@ description: The set of commands pushed to the remote device returned: always type: list - sample: ['command 1', 'command 2', 'command 3'] + sample: ['interface GigabitEthernet 0/1', 'description This is test', 'speed 100'] """ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ios.config.interfaces.interfaces import Interfaces +from ansible.module_utils.network.ios.argspec.interfaces.interfaces import InterfacesArgs +from ansible.module_utils.network.ios.config.interfaces.interfaces import Interfaces def main(): @@ -345,12 +347,13 @@ def main(): :returns: the result form module invocation """ - module = AnsibleModule(argument_spec=Interfaces.argument_spec, + module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec, supports_check_mode=True) result = Interfaces(module).execute_module() module.exit_json(**result) + if __name__ == '__main__': main() diff --git a/module_utils/ios/argspec/facts/facts.py b/module_utils/ios/argspec/facts/facts.py deleted file mode 100644 index 9ed0376..0000000 --- a/module_utils/ios/argspec/facts/facts.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The arg spec for the ios facts module. -""" - -class FactsArgs(object): #pylint: disable=R0903 - """ The arg spec for the ios facts module - """ - def __init__(self, **kwargs): - pass - - choices = [ - 'all', - 'interfaces', - ] - argument_spec = { - 'gather_subset': dict(default=['!config'], type='list'), - 'gather_network_resources': dict(default=['all'], choices=choices, type='list'), - } diff --git a/module_utils/ios/argspec/interfaces/interfaces.py b/module_utils/ios/argspec/interfaces/interfaces.py deleted file mode 100644 index 3410094..0000000 --- a/module_utils/ios/argspec/interfaces/interfaces.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -############################################# -################# WARNING #################### -############################################## -### -### This file is auto generated by the resource -### module builder playbook. -### -### Do not edit this file manually. -### -### Changes to this file will be over written -### by the resource module builder. -### -### Changes should be made in the model used to -### generate this file or in the resource module -### builder template. -### -############################################## -############################################## -############################################## -""" -The arg spec for the ios_interfaces module -""" - -class InterfacesArgs(object): - - def __init__(self, **kwargs): - pass - - config_spec = { - 'name': dict(type='str', required=True), - 'description': dict(), - 'enabled': dict(default=True, type=bool), - 'speed': dict(), - 'mtu': dict(), - 'duplex': dict(choices=['full', 'half', 'auto']), - } - - argument_spec = { - 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), - 'config': dict(type='list', elements='dict', options=config_spec) - } - diff --git a/module_utils/ios/config/base.py b/module_utils/ios/config/base.py deleted file mode 100644 index 1ba31ed..0000000 --- a/module_utils/ios/config/base.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The base class for all ios resource modules -""" - -from ansible.module_utils.connection import Connection - -class ConfigBase(object): #pylint: disable=R0205,R0903 - """ The base class for all ios resource modules - """ - _connection = None - - def __init__(self, module): - self._module = module - self._connection = self._get_connection() - - def _get_connection(self): - if self._connection: - return self._connection - self._connection = Connection(self._module._socket_path) #pylint: disable=W0212 - return self._connection diff --git a/module_utils/ios/facts/base.py b/module_utils/ios/facts/base.py deleted file mode 100644 index acdd38f..0000000 --- a/module_utils/ios/facts/base.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The ios facts base class -this contains methods common to all facts subsets -""" - -import re -from copy import deepcopy -from ansible.module_utils.six import iteritems - - -class FactsBase(object): - """ - The ios facts base class - """ - generated_spec = {} - ansible_facts = {'ansible_network_resources': {}} - - def __init__(self, argspec, subspec=None, options=None): - spec = deepcopy(argspec) - if subspec: - if options: - facts_argument_spec = spec[subspec][options] - else: - facts_argument_spec = spec[subspec] - else: - facts_argument_spec = spec - - self.generated_spec = self.generate_dict(facts_argument_spec) - - @staticmethod - def generate_dict(spec): - """ - Generate dictionary which is in sync with argspec - - :param spec: A dictionary which the argspec of module - :rtype: A dictionary - :returns: A dictionary in sync with argspec with default value - """ - obj = {} - if not spec: - return obj - - for key, val in iteritems(spec): - if 'default' in val: - dct = {key: val['default']} - else: - dct = {key: None} - obj.update(dct) - - return obj - - @staticmethod - def parse_conf_arg(cfg, arg): - """ - Parse config based on argument - - :param cfg: A text string which is a line of configuration. - :param arg: A text string which is to be matched. - :rtype: A text string - :returns: A text string if match is found - """ - match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) - if match: - result = match.group(1).strip() - else: - result = None - return result - - @staticmethod - def parse_conf_cmd_arg(cfg, cmd, res1, res2=None): - """ - Parse config based on command - - :param cfg: A text string which is a line of configuration. - :param cmd: A text string which is the command to be matched - :param res1: A text string to be returned if the command is present - :param res2: A text string to be returned if the negate command is present - :rtype: A text string - :returns: A text string if match is found - """ - match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) - if match: - return res1 - if res2 is not None: - match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) - if match: - return res2 - return None - - @staticmethod - def generate_final_config(cfg_dict): - """ - Generate final config dictionary - - :param cfg_dict: A dictionary parsed in the facts system - :rtype: A dictionary - :returns: A dictionary by elimating keys that have null values - """ - final_cfg = {} - if not cfg_dict: - return final_cfg - - for key, val in iteritems(cfg_dict): - if val is not None: - final_cfg.update({key:val}) - return final_cfg diff --git a/module_utils/ios/facts/facts.py b/module_utils/ios/facts/facts.py deleted file mode 100644 index d89ad02..0000000 --- a/module_utils/ios/facts/facts.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The facts class for ios -this file validates each subset of facts and selectively -calls the appropriate facts gathering function -""" - -from ansible.module_utils.six import iteritems - -from ansible.module_utils.ios.argspec.facts.facts import FactsArgs -from ansible.module_utils.ios.facts.base import FactsBase -from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs -from ansible.module_utils.ios.facts.interfaces.interfaces import InterfacesFacts - - -FACT_SUBSETS = {} - -class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 - """ The fact class for ios - """ - - VALID_GATHER_SUBSETS = frozenset(FACT_SUBSETS.keys()) - - def generate_runable_subsets(self, module, subsets, valid_subsets): - runable_subsets = set() - exclude_subsets = set() - minimal_gather_subset = frozenset(['default']) - - for subset in subsets: - if subset == 'all': - runable_subsets.update(valid_subsets) - continue - if subset == 'min' and minimal_gather_subset: - runable_subsets.update(minimal_gather_subset) - continue - if subset.startswith('!'): - subset = subset[1:] - if subset == 'min': - exclude_subsets.update(minimal_gather_subset) - continue - if subset == 'all': - exclude_subsets.update(valid_subsets - minimal_gather_subset) - continue - exclude = True - else: - exclude = False - - if subset not in valid_subsets: - module.fail_json(msg='Bad subset') - - if exclude: - exclude_subsets.add(subset) - else: - runable_subsets.add(subset) - - if not runable_subsets: - runable_subsets.update(valid_subsets) - runable_subsets.difference_update(exclude_subsets) - - return runable_subsets - - def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): - """ Collect the facts for ios - :param module: The module instance - :param connection: The device connection - :param gather_subset: The facts subset to collect - :param gather_network_resources: The resource subset to collect - :rtype: dict - :returns: the facts gathered - """ - warnings = [] - self.ansible_facts['gather_network_resources'] = list() - self.ansible_facts['gather_subset'] = list() - - valid_network_resources_subsets = self.argument_spec['gather_network_resources'].get('choices', []) - if valid_network_resources_subsets and 'all' in valid_network_resources_subsets: - valid_network_resources_subsets.remove('all') - - if valid_network_resources_subsets: - resources_runable_subsets = self.generate_runable_subsets(module, gather_network_resources, valid_network_resources_subsets) - if resources_runable_subsets: - self.ansible_facts['gather_network_resources'] = list(resources_runable_subsets) - for attr in resources_runable_subsets: - getattr(self, '_get_%s' % attr, {})(module, connection) - if self.VALID_GATHER_SUBSETS: - runable_subsets = self.generate_runable_subsets(module, gather_subset, self.VALID_GATHER_SUBSETS) - - if runable_subsets: - facts = dict() - self.ansible_facts['gather_subset'] = list(runable_subsets) - - instances = list() - for key in runable_subsets: - instances.append(FACT_SUBSETS[key](module)) - - for inst in instances: - inst.populate() - facts.update(inst.facts) - warnings.extend(inst.warnings) - - for key, value in iteritems(facts): - key = 'ansible_net_%s' % key - self.ansible_facts[key] = value - - if warnings: - return self.ansible_facts, warnings - else: - return self.ansible_facts - - - @staticmethod - def _get_interfaces(module, connection): - return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) \ No newline at end of file diff --git a/module_utils/ios/facts/interfaces/interfaces.py b/module_utils/ios/facts/interfaces/interfaces.py deleted file mode 100644 index 88cea5e..0000000 --- a/module_utils/ios/facts/interfaces/interfaces.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The ios interfaces fact class -It is in this file the configuration is collected from the device -for a given resource, parsed, and the facts tree is populated -based on the configuration. -""" - -import re -from copy import deepcopy - -from ansible.module_utils.ios.facts.base import FactsBase -from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface - - -class InterfacesFacts(FactsBase): - """ The ios interfaces fact class - """ - - def populate_facts(self, module, connection, data=None): - """ Populate the facts for interfaces - - :param module: the module instance - :param connection: the device connection - :param data: previously collected conf - :rtype: dictionary - :returns: facts - """ - objs = [] - - if not data: - data = connection.get('show running-config | section ^interface') - # operate on a collection of resource x - config = data.split('interface ') - for conf in config: - if conf: - obj = self.render_config(self.generated_spec, conf) - if obj: - objs.append(obj) - facts = {} - - if objs: - facts['interfaces'] = objs - self.ansible_facts['ansible_network_resources'].update(facts) - return self.ansible_facts - - def render_config(self, spec, conf): - """ - Render config as dictionary structure and delete keys from spec for null values - - :param spec: The facts tree, generated from the argspec - :param conf: The configuration - :rtype: dictionary - :returns: The generated config - """ - config = deepcopy(spec) - match = re.search(r'^(\S+)', conf) - intf = match.group(1) - - if get_interface_type(intf) == 'unknown': - return {} - # populate the facts from the configuration - config['name'] = normalize_interface(intf) - config['description'] = self.parse_conf_arg(conf, 'description') - config['speed'] = self.parse_conf_arg(conf, 'speed') - config['mtu'] = self.parse_conf_arg(conf, 'mtu') - config['duplex'] = self.parse_conf_arg(conf, 'duplex') - enabled = self.parse_conf_cmd_arg(conf, 'shutdown', False) - config['enabled'] = enabled if enabled is not None else config['enabled'] - - return self.generate_final_config(config) diff --git a/module_utils/network/argspec/__init__.py b/module_utils/network/argspec/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/network/argspec/base.py b/module_utils/network/argspec/base.py deleted file mode 100644 index 89fc67a..0000000 --- a/module_utils/network/argspec/base.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" The argspec base class -""" - -class ArgspecBase(object): #pylint: disable=R0205,R0903 - """ The argspec base class - """ - def __init__(self, **kwargs): - pass diff --git a/module_utils/ios/__init__.py b/module_utils/network/ios/__init__.py similarity index 100% rename from module_utils/ios/__init__.py rename to module_utils/network/ios/__init__.py diff --git a/module_utils/ios/argspec/__init__.py b/module_utils/network/ios/argspec/__init__.py similarity index 100% rename from module_utils/ios/argspec/__init__.py rename to module_utils/network/ios/argspec/__init__.py diff --git a/module_utils/ios/argspec/facts/__init__.py b/module_utils/network/ios/argspec/facts/__init__.py similarity index 100% rename from module_utils/ios/argspec/facts/__init__.py rename to module_utils/network/ios/argspec/facts/__init__.py diff --git a/module_utils/network/ios/argspec/facts/facts.py b/module_utils/network/ios/argspec/facts/facts.py new file mode 100644 index 0000000..e01301e --- /dev/null +++ b/module_utils/network/ios/argspec/facts/facts.py @@ -0,0 +1,28 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The arg spec for the ios facts module. +""" + + +class FactsArgs(object): + """ The arg spec for the ios facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'interfaces', + ] + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(default=['all'], + choices=choices, + type='list'), + } \ No newline at end of file diff --git a/module_utils/ios/argspec/interfaces/__init__.py b/module_utils/network/ios/argspec/interfaces/__init__.py similarity index 100% rename from module_utils/ios/argspec/interfaces/__init__.py rename to module_utils/network/ios/argspec/interfaces/__init__.py diff --git a/module_utils/network/ios/argspec/interfaces/interfaces.py b/module_utils/network/ios/argspec/interfaces/interfaces.py new file mode 100644 index 0000000..a770032 --- /dev/null +++ b/module_utils/network/ios/argspec/interfaces/interfaces.py @@ -0,0 +1,44 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the ios_interfaces module +""" + + +class InterfacesArgs(object): + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', + 'options': {'name': {'type': 'str', 'required':True}, + 'description': {'type': 'str'}, + 'enabled': {'type': bool}, + 'speed': {'type': 'str'}, + 'mtu': {'type': 'str'}, + 'duplex': {'type': 'str', 'choices': ['full', 'half', 'auto']}}, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} diff --git a/module_utils/ios/config/__init__.py b/module_utils/network/ios/config/__init__.py similarity index 100% rename from module_utils/ios/config/__init__.py rename to module_utils/network/ios/config/__init__.py diff --git a/module_utils/ios/config/interfaces/__init__.py b/module_utils/network/ios/config/interfaces/__init__.py similarity index 100% rename from module_utils/ios/config/interfaces/__init__.py rename to module_utils/network/ios/config/interfaces/__init__.py diff --git a/module_utils/ios/config/interfaces/interfaces.py b/module_utils/network/ios/config/interfaces/interfaces.py similarity index 77% rename from module_utils/ios/config/interfaces/interfaces.py rename to module_utils/network/ios/config/interfaces/interfaces.py index 99e5d02..c3f4e95 100644 --- a/module_utils/ios/config/interfaces/interfaces.py +++ b/module_utils/network/ios/config/interfaces/interfaces.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +# # -*- coding: utf-8 -*- # Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -11,15 +11,21 @@ """ -from ansible.module_utils.network.common.utils import to_list +# from ansible.module_utils.network.common.utils import to_list +# +# from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs +# from ansible.module_utils.ios.config.base import ConfigBase +# from ansible.module_utils.ios.facts.facts import Facts +# from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list +from ansible.module_utils.six import iteritems -from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs -from ansible.module_utils.ios.config.base import ConfigBase -from ansible.module_utils.ios.facts.facts import Facts -from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.ios.facts.facts import Facts +from ansible.module_utils.network.ios.utils.utils import get_interface_type -class Interfaces(ConfigBase, InterfacesArgs): +class Interfaces(ConfigBase): """ The ios_interfaces class """ @@ -35,14 +41,16 @@ class Interfaces(ConfigBase, InterfacesArgs): params = ('description', 'mtu', 'speed', 'duplex') + def __init__(self, module): + super(Interfaces, self).__init__(module) + def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) :rtype: A dictionary :returns: The current configuration as a dictionary """ - result = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) - facts = result + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) interfaces_facts = facts['ansible_network_resources'].get('interfaces') if not interfaces_facts: return [] @@ -59,8 +67,8 @@ def execute_module(self): commands = list() warnings = list() - existing_facts = self.get_interfaces_facts() - commands.extend(self.set_config(existing_facts)) + existing_interfaces_facts = self.get_interfaces_facts() + commands.extend(self.set_config(existing_interfaces_facts)) if commands: if not self._module.check_mode: @@ -68,16 +76,16 @@ def execute_module(self): result['changed'] = True result['commands'] = commands - interfaces_facts = self.get_interfaces_facts() + changed_interfaces_facts = self.get_interfaces_facts() - result['before'] = existing_facts + result['before'] = existing_interfaces_facts if result['changed']: - result['after'] = interfaces_facts + result['after'] = changed_interfaces_facts result['warnings'] = warnings return result - def set_config(self, existing_facts): + def set_config(self, existing_interfaces_facts): """ Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) @@ -86,12 +94,8 @@ def set_config(self, existing_facts): to the deisred configuration """ want = self._module.params['config'] - - for w in want: - w.update({'name': normalize_interface(w['name'])}) - have = existing_facts + have = existing_interfaces_facts resp = self.set_state(want, have) - return to_list(resp) def set_state(self, want, have): @@ -107,22 +111,18 @@ def set_state(self, want, have): state = self._module.params['state'] if state == 'overridden': - kwargs = {'want': want, 'have': have} - commands = self._state_overridden(**kwargs) + commands = self._state_overridden(want, have) elif state == 'deleted': - kwargs = {'want': want, 'have': have} - commands = self._state_deleted(**kwargs) + commands = self._state_deleted(want, have) elif state == 'merged': - kwargs = {'want': want, 'have': have} - commands = self._state_merged(**kwargs) + commands = self._state_merged(want, have) elif state == 'replaced': - kwargs = {'want': want, 'have': have} - commands = self._state_replaced(**kwargs) + commands = self._state_replaced(want, have) return commands @staticmethod - def _state_replaced(**kwargs): + def _state_replaced(want, have): """ The command generator when state is replaced :param want: the desired configuration as a dictionary @@ -133,8 +133,6 @@ def _state_replaced(**kwargs): to the deisred configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for interface in want: for each in have: @@ -144,15 +142,13 @@ def _state_replaced(**kwargs): break else: continue - kwargs = {'want': interface, 'have': each, } - commands.extend(Interfaces.clear_interface(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands} commands.extend(Interfaces.set_interface(**kwargs)) return commands @staticmethod - def _state_overridden(**kwargs): + def _state_overridden(want, have): """ The command generator when state is overridden :param want: the desired configuration as a dictionary @@ -162,8 +158,6 @@ def _state_overridden(**kwargs): to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for each in have: for interface in want: @@ -178,15 +172,13 @@ def _state_overridden(**kwargs): kwargs = {'want': interface, 'have': each} commands.extend(Interfaces.clear_interface(**kwargs)) continue - kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands} commands.extend(Interfaces.set_interface(**kwargs)) return commands @staticmethod - def _state_merged(**kwargs): + def _state_merged(want, have): """ The command generator when state is merged :param want: the additive configuration as a dictionary @@ -196,8 +188,6 @@ def _state_merged(**kwargs): the current configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for interface in want: for each in have: @@ -211,7 +201,7 @@ def _state_merged(**kwargs): return commands @staticmethod - def _state_deleted(**kwargs): + def _state_deleted(want, have): """ The command generator when state is deleted :param want: the objects from which the configuration should be removed @@ -222,18 +212,21 @@ def _state_deleted(**kwargs): of the provided objects """ commands = [] - want = kwargs['want'] - have = kwargs['have'] - for interface in want: + if want: + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) + else: for each in have: - if each['name'] == interface['name']: - break - else: - continue - interface = dict(name=interface['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + kwargs = {'want': {}, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) return commands @@ -284,8 +277,12 @@ def clear_interface(**kwargs): commands = [] want = kwargs['want'] have = kwargs['have'] - interface_type = get_interface_type(want['name']) - interface = 'interface ' + want['name'] + if want.get('name'): + interface_type = get_interface_type(want['name']) + interface = 'interface ' + want['name'] + else: + interface_type = get_interface_type(have['name']) + interface = 'interface ' + have['name'] if have.get('description') and want.get('description') != have.get('description'): Interfaces._remove_command_from_interface(interface, 'description', commands) @@ -301,4 +298,4 @@ def clear_interface(**kwargs): if have.get('mtu') and want.get('mtu') != have.get('mtu'): Interfaces._remove_command_from_interface(interface, 'mtu', commands) - return commands \ No newline at end of file + return commands diff --git a/module_utils/ios/facts/__init__.py b/module_utils/network/ios/facts/__init__.py similarity index 100% rename from module_utils/ios/facts/__init__.py rename to module_utils/network/ios/facts/__init__.py diff --git a/module_utils/network/ios/facts/facts.py b/module_utils/network/ios/facts/facts.py new file mode 100644 index 0000000..3885105 --- /dev/null +++ b/module_utils/network/ios/facts/facts.py @@ -0,0 +1,48 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The facts class for ios +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from ansible.module_utils.network.ios.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.common.facts.facts import FactsBase +from ansible.module_utils.network.ios.facts.interfaces.interfaces import InterfacesFacts + + +FACT_LEGACY_SUBSETS = {} +FACT_RESOURCE_SUBSETS = dict( + interfaces=InterfacesFacts, +) + + +class Facts(FactsBase): + """ The fact class for ios + """ + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): + """ Collect the facts for ios + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', []) + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS, resource_facts_type, data) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) + + return self.ansible_facts, self._warnings \ No newline at end of file diff --git a/module_utils/ios/facts/interfaces/__init__.py b/module_utils/network/ios/facts/interfaces/__init__.py similarity index 100% rename from module_utils/ios/facts/interfaces/__init__.py rename to module_utils/network/ios/facts/interfaces/__init__.py diff --git a/module_utils/network/ios/facts/interfaces/interfaces.py b/module_utils/network/ios/facts/interfaces/interfaces.py new file mode 100644 index 0000000..6717de8 --- /dev/null +++ b/module_utils/network/ios/facts/interfaces/interfaces.py @@ -0,0 +1,94 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The ios vlans fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from copy import deepcopy +import re +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.ios.utils.utils import get_interface_type, normalize_interface +from ansible.module_utils.network.ios.argspec.interfaces.interfaces import InterfacesArgs + + +class InterfacesFacts(object): + """ The ios interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = InterfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for vlans + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if connection: + pass + objs = [] + + if not data: + data = connection.get('show running-config | section ^interface') + # operate on a collection of resource x + config = data.split('interface ') + for conf in config: + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + facts = {} + + if objs: + facts['interfaces'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['interfaces'].append(utils.remove_empties(cfg)) + ansible_facts['ansible_network_resources'].update(facts) + + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + match = re.search(r'^(\S+)', conf) + intf = match.group(1) + + if get_interface_type(intf) == 'unknown': + return {} + # populate the facts from the configuration + config['name'] = normalize_interface(intf) + config['description'] = utils.parse_conf_arg(conf, 'description') + config['speed'] = utils.parse_conf_arg(conf, 'speed') + config['mtu'] = utils.parse_conf_arg(conf, 'mtu') + config['duplex'] = utils.parse_conf_arg(conf, 'duplex') + enabled = utils.parse_conf_cmd_arg(conf, 'shutdown', False) + config['enabled'] = enabled if enabled is not None else config['enabled'] + + return utils.remove_empties(config) diff --git a/module_utils/ios/utils/__init__.py b/module_utils/network/ios/utils/__init__.py similarity index 100% rename from module_utils/ios/utils/__init__.py rename to module_utils/network/ios/utils/__init__.py diff --git a/module_utils/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py similarity index 94% rename from module_utils/ios/utils/utils.py rename to module_utils/network/ios/utils/utils.py index 4cffe84..d87f3e9 100644 --- a/module_utils/ios/utils/utils.py +++ b/module_utils/network/ios/utils/utils.py @@ -1,7 +1,8 @@ -#!/usr/bin/python +# # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # utils From f7afa3bf69cf1aad712eccbb88c0c1dc762d0adf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 30 Jul 2019 15:17:12 +0530 Subject: [PATCH 51/53] fix interfaces module --- .../ios/config/interfaces/interfaces.py | 91 ++++++++++++++----- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/module_utils/network/ios/config/interfaces/interfaces.py b/module_utils/network/ios/config/interfaces/interfaces.py index c3f4e95..a84915e 100644 --- a/module_utils/network/ios/config/interfaces/interfaces.py +++ b/module_utils/network/ios/config/interfaces/interfaces.py @@ -142,8 +142,13 @@ def _state_replaced(want, have): break else: continue - kwargs = {'want': interface, 'have': each, 'commands': commands} - commands.extend(Interfaces.set_interface(**kwargs)) + have_dict = Interfaces._filter_dict_having_none_value(interface, each) + kwargs = {'want': {}, 'have': have_dict} + commands.extend(Interfaces._clear_config(**kwargs)) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces._set_config(**kwargs)) + # Remove the duplicate interface call + commands = Interfaces._remove_duplicate_interface(commands) return commands @@ -170,10 +175,15 @@ def _state_overridden(want, have): # pretend we recieved an empty desired state. interface = dict(name=each['name']) kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + commands.extend(Interfaces._clear_config(**kwargs)) continue - kwargs = {'want': interface, 'have': each, 'commands': commands} - commands.extend(Interfaces.set_interface(**kwargs)) + have_dict = Interfaces._filter_dict_having_none_value(interface, each) + kwargs = {'want': {}, 'have': have_dict} + commands.extend(Interfaces._clear_config(**kwargs)) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces._set_config(**kwargs)) + # Remove the duplicate interface call + commands = Interfaces._remove_duplicate_interface(commands) return commands @@ -196,7 +206,7 @@ def _state_merged(want, have): else: continue kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.set_interface(**kwargs)) + commands.extend(Interfaces._set_config(**kwargs)) return commands @@ -222,11 +232,11 @@ def _state_deleted(want, have): continue interface = dict(name=interface['name']) kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + commands.extend(Interfaces._clear_config(**kwargs)) else: for each in have: kwargs = {'want': {}, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + commands.extend(Interfaces._clear_config(**kwargs)) return commands @@ -246,33 +256,64 @@ def _add_command_to_interface(interface, cmd, commands): commands.append(cmd) @staticmethod - def set_interface(**kwargs): + def _dict_diff(sample_dict): + # Generate a set with passed dictionary for comparison + test_dict = {} + for k, v in iteritems(sample_dict): + if v is not None: + test_dict.update({k: v}) + return_set = set(tuple(test_dict.items())) + return return_set + + @staticmethod + def _filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_dict['name'] = want.get('name') + for k, v in iteritems(want): + if v is None: + val = have.get(k) + test_dict.update({k: val}) + return test_dict + + @staticmethod + def _remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if 'interface' in each: + interface = each + if interface not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + @staticmethod + def _set_config(**kwargs): # Set the interface config based on the want and have config commands = [] want = kwargs['want'] have = kwargs['have'] - clear_cmds = [] - - if kwargs.get('commands'): - clear_cmds = kwargs['commands'] - interface = 'interface ' + want['name'] - if want.get('enabled') and want.get('enabled') != have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'no shutdown', commands) - elif not want.get('enabled') and want.get('enabled') != have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'shutdown', commands) - for item in Interfaces.params: - cmd = 'no ' + item - candidate = want.get(item) - if candidate and (candidate != have.get(item) or cmd in clear_cmds): - cmd = item + ' ' + str(candidate) - Interfaces._add_command_to_interface(interface, cmd, commands) + # Get the diff b/w want and have + want_dict = Interfaces._dict_diff(want) + have_dict = Interfaces._dict_diff(have) + diff = want_dict - have_dict + + if diff: + diff = dict(diff) + for item in Interfaces.params: + if diff.get(item): + cmd = item + ' ' + str(want.get(item)) + Interfaces._add_command_to_interface(interface, cmd, commands) return commands @staticmethod - def clear_interface(**kwargs): + def _clear_config(**kwargs): # Delete the interface config based on the want and have config commands = [] want = kwargs['want'] From 3dfdf8f4a273ebe26867a35eee80af6d789216cc Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 30 Jul 2019 15:30:22 +0530 Subject: [PATCH 52/53] move common code to util --- .../ios/config/interfaces/interfaces.py | 87 ++++--------------- module_utils/network/ios/utils/utils.py | 52 +++++++++++ 2 files changed, 67 insertions(+), 72 deletions(-) diff --git a/module_utils/network/ios/config/interfaces/interfaces.py b/module_utils/network/ios/config/interfaces/interfaces.py index a84915e..a6ae747 100644 --- a/module_utils/network/ios/config/interfaces/interfaces.py +++ b/module_utils/network/ios/config/interfaces/interfaces.py @@ -10,19 +10,12 @@ created """ - -# from ansible.module_utils.network.common.utils import to_list -# -# from ansible.module_utils.ios.argspec.interfaces.interfaces import InterfacesArgs -# from ansible.module_utils.ios.config.base import ConfigBase -# from ansible.module_utils.ios.facts.facts import Facts -# from ansible.module_utils.ios.utils.utils import get_interface_type, normalize_interface, search_obj_in_list -from ansible.module_utils.six import iteritems - from ansible.module_utils.network.common.cfg.base import ConfigBase from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.network.ios.facts.facts import Facts -from ansible.module_utils.network.ios.utils.utils import get_interface_type +from ansible.module_utils.network.ios.utils.utils import get_interface_type, dict_diff +from ansible.module_utils.network.ios.utils.utils import remove_command_from_interface, add_command_to_interface +from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface class Interfaces(ConfigBase): @@ -142,13 +135,13 @@ def _state_replaced(want, have): break else: continue - have_dict = Interfaces._filter_dict_having_none_value(interface, each) + have_dict = filter_dict_having_none_value(interface, each) kwargs = {'want': {}, 'have': have_dict} commands.extend(Interfaces._clear_config(**kwargs)) kwargs = {'want': interface, 'have': each} commands.extend(Interfaces._set_config(**kwargs)) # Remove the duplicate interface call - commands = Interfaces._remove_duplicate_interface(commands) + commands = remove_duplicate_interface(commands) return commands @@ -177,13 +170,13 @@ def _state_overridden(want, have): kwargs = {'want': interface, 'have': each} commands.extend(Interfaces._clear_config(**kwargs)) continue - have_dict = Interfaces._filter_dict_having_none_value(interface, each) + have_dict = filter_dict_having_none_value(interface, each) kwargs = {'want': {}, 'have': have_dict} commands.extend(Interfaces._clear_config(**kwargs)) kwargs = {'want': interface, 'have': each} commands.extend(Interfaces._set_config(**kwargs)) # Remove the duplicate interface call - commands = Interfaces._remove_duplicate_interface(commands) + commands = remove_duplicate_interface(commands) return commands @@ -240,56 +233,6 @@ def _state_deleted(want, have): return commands - @staticmethod - def _remove_command_from_interface(interface, cmd, commands): - # To delete the passed config - if interface not in commands: - commands.insert(0, interface) - commands.append('no %s' % cmd) - return commands - - @staticmethod - def _add_command_to_interface(interface, cmd, commands): - # To set the passed config - if interface not in commands: - commands.insert(0, interface) - commands.append(cmd) - - @staticmethod - def _dict_diff(sample_dict): - # Generate a set with passed dictionary for comparison - test_dict = {} - for k, v in iteritems(sample_dict): - if v is not None: - test_dict.update({k: v}) - return_set = set(tuple(test_dict.items())) - return return_set - - @staticmethod - def _filter_dict_having_none_value(want, have): - # Generate dict with have dict value which is None in want dict - test_dict = dict() - test_dict['name'] = want.get('name') - for k, v in iteritems(want): - if v is None: - val = have.get(k) - test_dict.update({k: val}) - return test_dict - - @staticmethod - def _remove_duplicate_interface(commands): - # Remove duplicate interface from commands - set_cmd = [] - for each in commands: - if 'interface' in each: - interface = each - if interface not in set_cmd: - set_cmd.append(each) - else: - set_cmd.append(each) - - return set_cmd - @staticmethod def _set_config(**kwargs): # Set the interface config based on the want and have config @@ -299,8 +242,8 @@ def _set_config(**kwargs): interface = 'interface ' + want['name'] # Get the diff b/w want and have - want_dict = Interfaces._dict_diff(want) - have_dict = Interfaces._dict_diff(have) + want_dict = dict_diff(want) + have_dict = dict_diff(have) diff = want_dict - have_dict if diff: @@ -308,7 +251,7 @@ def _set_config(**kwargs): for item in Interfaces.params: if diff.get(item): cmd = item + ' ' + str(want.get(item)) - Interfaces._add_command_to_interface(interface, cmd, commands) + add_command_to_interface(interface, cmd, commands) return commands @@ -326,17 +269,17 @@ def _clear_config(**kwargs): interface = 'interface ' + have['name'] if have.get('description') and want.get('description') != have.get('description'): - Interfaces._remove_command_from_interface(interface, 'description', commands) + remove_command_from_interface(interface, 'description', commands) if not have.get('enabled') and want.get('enabled') != have.get('enabled'): # if enable is False set enable as True which is the default behavior - Interfaces._remove_command_from_interface(interface, 'shutdown', commands) + remove_command_from_interface(interface, 'shutdown', commands) if interface_type.lower() == 'gigabitethernet': if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'): - Interfaces._remove_command_from_interface(interface, 'speed', commands) + remove_command_from_interface(interface, 'speed', commands) if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'): - Interfaces._remove_command_from_interface(interface, 'duplex', commands) + remove_command_from_interface(interface, 'duplex', commands) if have.get('mtu') and want.get('mtu') != have.get('mtu'): - Interfaces._remove_command_from_interface(interface, 'mtu', commands) + remove_command_from_interface(interface, 'mtu', commands) return commands diff --git a/module_utils/network/ios/utils/utils.py b/module_utils/network/ios/utils/utils.py index d87f3e9..6786c23 100644 --- a/module_utils/network/ios/utils/utils.py +++ b/module_utils/network/ios/utils/utils.py @@ -6,6 +6,58 @@ # utils +from ansible.module_utils.six import iteritems + + +def remove_command_from_interface(interface, cmd, commands): + # To delete the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + + +def add_command_to_interface(interface, cmd, commands): + # To set the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + + +def dict_diff(sample_dict): + # Generate a set with passed dictionary for comparison + test_dict = {} + for k, v in iteritems(sample_dict): + if v is not None: + test_dict.update({k: v}) + return_set = set(tuple(test_dict.items())) + return return_set + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_dict['name'] = want.get('name') + for k, v in iteritems(want): + if v is None: + val = have.get(k) + test_dict.update({k: val}) + return test_dict + + +def remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if 'interface' in each: + interface = each + if interface not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + def search_obj_in_list(name, lst): for o in lst: From c00244dbfd53d5fd715a7796b08ff38c986d6811 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Wed, 31 Jul 2019 14:02:10 +0530 Subject: [PATCH 53/53] fix all issues --- .../network/ios/argspec/interfaces/interfaces.py | 2 +- .../network/ios/config/interfaces/interfaces.py | 13 +++++++++++-- .../network/ios/facts/interfaces/interfaces.py | 5 +++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/module_utils/network/ios/argspec/interfaces/interfaces.py b/module_utils/network/ios/argspec/interfaces/interfaces.py index a770032..baaf65f 100644 --- a/module_utils/network/ios/argspec/interfaces/interfaces.py +++ b/module_utils/network/ios/argspec/interfaces/interfaces.py @@ -34,7 +34,7 @@ def __init__(self, **kwargs): argument_spec = {'config': {'elements': 'dict', 'options': {'name': {'type': 'str', 'required':True}, 'description': {'type': 'str'}, - 'enabled': {'type': bool}, + 'enabled': {'default': True, 'type': bool}, 'speed': {'type': 'str'}, 'mtu': {'type': 'str'}, 'duplex': {'type': 'str', 'choices': ['full', 'half', 'auto']}}, diff --git a/module_utils/network/ios/config/interfaces/interfaces.py b/module_utils/network/ios/config/interfaces/interfaces.py index a6ae747..3bfbaa6 100644 --- a/module_utils/network/ios/config/interfaces/interfaces.py +++ b/module_utils/network/ios/config/interfaces/interfaces.py @@ -16,7 +16,7 @@ from ansible.module_utils.network.ios.utils.utils import get_interface_type, dict_diff from ansible.module_utils.network.ios.utils.utils import remove_command_from_interface, add_command_to_interface from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface - +import q class Interfaces(ConfigBase): """ @@ -240,18 +240,27 @@ def _set_config(**kwargs): want = kwargs['want'] have = kwargs['have'] interface = 'interface ' + want['name'] + q(want, have, interface) + # if want.get('enabled') and want.get('enabled') != have.get('enabled'): + # add_command_to_interface(interface, 'no shutdown', commands) + # elif not want.get('enabled') and want.get('enabled') != have.get('enabled'): + # add_command_to_interface(interface, 'shutdown', commands) # Get the diff b/w want and have want_dict = dict_diff(want) have_dict = dict_diff(have) diff = want_dict - have_dict - + q(want_dict, have_dict, diff) if diff: diff = dict(diff) for item in Interfaces.params: if diff.get(item): cmd = item + ' ' + str(want.get(item)) add_command_to_interface(interface, cmd, commands) + if diff.get('enabled'):# and want.get('enabled') != have.get('enabled'): + add_command_to_interface(interface, 'no shutdown', commands) + elif diff.get('enabled') is False: + add_command_to_interface(interface, 'shutdown', commands) return commands diff --git a/module_utils/network/ios/facts/interfaces/interfaces.py b/module_utils/network/ios/facts/interfaces/interfaces.py index 6717de8..ad810ef 100644 --- a/module_utils/network/ios/facts/interfaces/interfaces.py +++ b/module_utils/network/ios/facts/interfaces/interfaces.py @@ -79,7 +79,7 @@ def render_config(self, spec, conf): config = deepcopy(spec) match = re.search(r'^(\S+)', conf) intf = match.group(1) - + import q if get_interface_type(intf) == 'unknown': return {} # populate the facts from the configuration @@ -89,6 +89,7 @@ def render_config(self, spec, conf): config['mtu'] = utils.parse_conf_arg(conf, 'mtu') config['duplex'] = utils.parse_conf_arg(conf, 'duplex') enabled = utils.parse_conf_cmd_arg(conf, 'shutdown', False) - config['enabled'] = enabled if enabled is not None else config['enabled'] + config['enabled'] = enabled if enabled is not None else True + q(config['name'], enabled, config['enabled']) return utils.remove_empties(config)