Initial commit

This commit is contained in:
Ansible Core Team 2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

View file

@ -0,0 +1,23 @@
#
# -*- 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 exos facts module.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class FactsArgs(object): # pylint: disable=R0903
""" The arg spec for the exos facts module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'gather_subset': dict(default=['!config'], type='list'),
'gather_network_resources': dict(type='list'),
}

View file

@ -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)
#############################################
# 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 exos_l2_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class L2_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the exos_l2_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'access': {'options': {'vlan': {'type': 'int'}},
'type': 'dict'},
'name': {'required': True, 'type': 'str'},
'trunk': {'options': {'native_vlan': {'type': 'int'}, 'trunk_allowed_vlans': {'type': 'list'}},
'type': 'dict'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], 'default': 'merged', 'type': 'str'}
} # pylint: disable=C0301

View file

@ -0,0 +1,57 @@
#
# -*- 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 exos_lldp_global module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Lldp_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the exos_lldp_global module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'interval': {'default': 30, 'type': 'int'},
'tlv_select': {
'options': {
'management_address': {'type': 'bool'},
'port_description': {'type': 'bool'},
'system_capabilities': {'type': 'bool'},
'system_description': {
'default': True,
'type': 'bool'},
'system_name': {'default': True, 'type': 'bool'}},
'type': 'dict'}},
'type': 'dict'},
'state': {
'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301

View file

@ -0,0 +1,49 @@
#
# -*- 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 exos_lldp_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Lldp_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the exos_lldp_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'enabled': {'type': 'bool'},
'name': {'required': True, 'type': 'str'}},
'type': 'list'},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301

View file

@ -0,0 +1,53 @@
#
# -*- 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 exos_vlans module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class VlansArgs(object): # pylint: disable=R0903
"""The arg spec for the exos_vlans module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'name': {'type': 'str'},
'state': {
'choices': ['active', 'suspend'],
'default': 'active',
'type': 'str'},
'vlan_id': {'required': True, 'type': 'int'}},
'type': 'list'},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301

View file

@ -0,0 +1,294 @@
#
# -*- 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 exos_l2_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 __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, dict_diff
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.facts import Facts
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
class L2_interfaces(ConfigBase):
"""
The exos_l2_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l2_interfaces',
]
L2_INTERFACE_NATIVE = {
"data": {
"openconfig-vlan:config": {
"interface-mode": "TRUNK",
"native-vlan": None,
"trunk-vlans": []
}
},
"method": "PATCH",
"path": None
}
L2_INTERFACE_TRUNK = {
"data": {
"openconfig-vlan:config": {
"interface-mode": "TRUNK",
"trunk-vlans": []
}
},
"method": "PATCH",
"path": None
}
L2_INTERFACE_ACCESS = {
"data": {
"openconfig-vlan:config": {
"interface-mode": "ACCESS",
"access-vlan": None
}
},
"method": "PATCH",
"path": None
}
L2_PATH = "/rest/restconf/data/openconfig-interfaces:interfaces/interface="
def __init__(self, module):
super(L2_interfaces, self).__init__(module)
def get_l2_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset, self.gather_network_resources)
l2_interfaces_facts = facts['ansible_network_resources'].get(
'l2_interfaces')
if not l2_interfaces_facts:
return []
return l2_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
requests = list()
existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
requests.extend(self.set_config(existing_l2_interfaces_facts))
if requests:
if not self._module.check_mode:
send_requests(self._module, requests=requests)
result['changed'] = True
result['requests'] = requests
changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
result['before'] = existing_l2_interfaces_facts
if result['changed']:
result['after'] = changed_l2_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_l2_interfaces_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 requests necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_l2_interfaces_facts
resp = self.set_state(want, have)
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 requests necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'overridden':
requests = self._state_overridden(want, have)
elif state == 'deleted':
requests = self._state_deleted(want, have)
elif state == 'merged':
requests = self._state_merged(want, have)
elif state == 'replaced':
requests = self._state_replaced(want, have)
return requests
def _state_replaced(self, want, have):
""" The request generator when state is replaced
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
for w in want:
for h in have:
if w["name"] == h["name"]:
if dict_diff(w, h):
l2_request = self._update_patch_request(w, h)
l2_request["data"] = json.dumps(l2_request["data"])
requests.append(l2_request)
break
return requests
def _state_overridden(self, want, have):
""" The request generator when state is overridden
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
have_copy = []
for w in want:
for h in have:
if w["name"] == h["name"]:
if dict_diff(w, h):
l2_request = self._update_patch_request(w, h)
l2_request["data"] = json.dumps(l2_request["data"])
requests.append(l2_request)
have_copy.append(h)
break
for h in have:
if h not in have_copy:
l2_delete = self._update_delete_request(h)
if l2_delete["path"]:
l2_delete["data"] = json.dumps(l2_delete["data"])
requests.append(l2_delete)
return requests
def _state_merged(self, want, have):
""" The request generator when state is merged
:rtype: A list
:returns: the requests necessary to merge the provided into
the current configuration
"""
requests = []
for w in want:
for h in have:
if w["name"] == h["name"]:
if dict_diff(h, w):
l2_request = self._update_patch_request(w, h)
l2_request["data"] = json.dumps(l2_request["data"])
requests.append(l2_request)
break
return requests
def _state_deleted(self, want, have):
""" The request generator when state is deleted
:rtype: A list
:returns: the requests necessary to remove the current configuration
of the provided objects
"""
requests = []
if want:
for w in want:
for h in have:
if w["name"] == h["name"]:
l2_delete = self._update_delete_request(h)
if l2_delete["path"]:
l2_delete["data"] = json.dumps(l2_delete["data"])
requests.append(l2_delete)
break
else:
for h in have:
l2_delete = self._update_delete_request(h)
if l2_delete["path"]:
l2_delete["data"] = json.dumps(l2_delete["data"])
requests.append(l2_delete)
return requests
def _update_patch_request(self, want, have):
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset, ['vlans', ])
vlans_facts = facts['ansible_network_resources'].get('vlans')
vlan_id = []
for vlan in vlans_facts:
vlan_id.append(vlan['vlan_id'])
if want.get("access"):
if want["access"]["vlan"] in vlan_id:
l2_request = deepcopy(self.L2_INTERFACE_ACCESS)
l2_request["data"]["openconfig-vlan:config"]["access-vlan"] = want["access"]["vlan"]
l2_request["path"] = self.L2_PATH + str(want["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config"
else:
self._module.fail_json(msg="VLAN %s does not exist" % (want["access"]["vlan"]))
elif want.get("trunk"):
if want["trunk"]["native_vlan"]:
if want["trunk"]["native_vlan"] in vlan_id:
l2_request = deepcopy(self.L2_INTERFACE_NATIVE)
l2_request["data"]["openconfig-vlan:config"]["native-vlan"] = want["trunk"]["native_vlan"]
l2_request["path"] = self.L2_PATH + str(want["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config"
for vlan in want["trunk"]["trunk_allowed_vlans"]:
if int(vlan) in vlan_id:
l2_request["data"]["openconfig-vlan:config"]["trunk-vlans"].append(int(vlan))
else:
self._module.fail_json(msg="VLAN %s does not exist" % (vlan))
else:
self._module.fail_json(msg="VLAN %s does not exist" % (want["trunk"]["native_vlan"]))
else:
l2_request = deepcopy(self.L2_INTERFACE_TRUNK)
l2_request["path"] = self.L2_PATH + str(want["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config"
for vlan in want["trunk"]["trunk_allowed_vlans"]:
if int(vlan) in vlan_id:
l2_request["data"]["openconfig-vlan:config"]["trunk-vlans"].append(int(vlan))
else:
self._module.fail_json(msg="VLAN %s does not exist" % (vlan))
return l2_request
def _update_delete_request(self, have):
l2_request = deepcopy(self.L2_INTERFACE_ACCESS)
if have["access"] and have["access"]["vlan"] != 1 or have["trunk"] or not have["access"]:
l2_request["data"]["openconfig-vlan:config"]["access-vlan"] = 1
l2_request["path"] = self.L2_PATH + str(have["name"]) + "/openconfig-if-ethernet:ethernet/openconfig-vlan:switched-vlan/config"
return l2_request

View file

@ -0,0 +1,199 @@
#
# -*- 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 exos_lldp_global 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 __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.facts import Facts
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
import json
from copy import deepcopy
class Lldp_global(ConfigBase):
"""
The exos_lldp_global class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lldp_global',
]
LLDP_DEFAULT_INTERVAL = 30
LLDP_DEFAULT_TLV = {
'system_name': True,
'system_description': True,
'system_capabilities': False,
'port_description': False,
'management_address': False
}
LLDP_REQUEST = {
"data": {"openconfig-lldp:config": {}},
"method": "PUT",
"path": "/rest/restconf/data/openconfig-lldp:lldp/config"
}
def __init__(self, module):
super(Lldp_global, self).__init__(module)
def get_lldp_global_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset, self.gather_network_resources)
lldp_global_facts = facts['ansible_network_resources'].get('lldp_global')
if not lldp_global_facts:
return {}
return lldp_global_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
requests = list()
existing_lldp_global_facts = self.get_lldp_global_facts()
requests.extend(self.set_config(existing_lldp_global_facts))
if requests:
if not self._module.check_mode:
send_requests(self._module, requests)
result['changed'] = True
result['requests'] = requests
changed_lldp_global_facts = self.get_lldp_global_facts()
result['before'] = existing_lldp_global_facts
if result['changed']:
result['after'] = changed_lldp_global_facts
result['warnings'] = warnings
return result
def set_config(self, existing_lldp_global_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 requests necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_lldp_global_facts
resp = self.set_state(want, have)
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 requests necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'deleted':
requests = self._state_deleted(want, have)
elif state == 'merged':
requests = self._state_merged(want, have)
elif state == 'replaced':
requests = self._state_replaced(want, have)
return requests
def _state_replaced(self, want, have):
""" The request generator when state is replaced
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
requests.extend(self._state_deleted(want, have))
requests.extend(self._state_merged(want, have))
return requests
def _state_merged(self, want, have):
""" The request generator when state is merged
:rtype: A list
:returns: the requests necessary to merge the provided into
the current configuration
"""
requests = []
request = deepcopy(self.LLDP_REQUEST)
self._update_lldp_config_body_if_diff(want, have, request)
if len(request["data"]["openconfig-lldp:config"]):
request["data"] = json.dumps(request["data"])
requests.append(request)
return requests
def _state_deleted(self, want, have):
""" The request generator when state is deleted
:rtype: A list
:returns: the requests necessary to remove the current configuration
of the provided objects
"""
requests = []
request = deepcopy(self.LLDP_REQUEST)
if want:
self._update_lldp_config_body_if_diff(want, have, request)
else:
if self.LLDP_DEFAULT_INTERVAL != have['interval']:
request["data"]["openconfig-lldp:config"].update(
{"hello-timer": self.LLDP_DEFAULT_INTERVAL})
if have['tlv_select'] != self.LLDP_DEFAULT_TLV:
request["data"]["openconfig-lldp:config"].update(
{"suppress-tlv-advertisement": [key.upper() for key, value in self.LLDP_DEFAULT_TLV.items() if not value]})
request["data"]["openconfig-lldp:config"]["suppress-tlv-advertisement"].sort()
if len(request["data"]["openconfig-lldp:config"]):
request["data"] = json.dumps(request["data"])
requests.append(request)
return requests
def _update_lldp_config_body_if_diff(self, want, have, request):
if want.get('interval'):
if want['interval'] != have['interval']:
request["data"]["openconfig-lldp:config"].update(
{"hello-timer": want['interval']})
if want.get('tlv_select'):
# Create list of TLVs to be suppressed which aren't already
want_suppress = [key.upper() for key, value in want["tlv_select"].items() if have["tlv_select"][key] != value and value is False]
if want_suppress:
# Add previously suppressed TLVs to the list as we are doing a PUT op
want_suppress.extend([key.upper() for key, value in have["tlv_select"].items() if value is False])
request["data"]["openconfig-lldp:config"].update(
{"suppress-tlv-advertisement": want_suppress})
request["data"]["openconfig-lldp:config"]["suppress-tlv-advertisement"].sort()

View file

@ -0,0 +1,243 @@
#
# -*- 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 exos_lldp_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 __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, dict_diff
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.facts import Facts
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
class Lldp_interfaces(ConfigBase):
"""
The exos_lldp_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lldp_interfaces',
]
LLDP_INTERFACE = {
"data": {
"openconfig-lldp:config": {
"name": None,
"enabled": True
}
},
"method": "PATCH",
"path": None
}
LLDP_PATH = "/rest/restconf/data/openconfig-lldp:lldp/interfaces/interface="
def __init__(self, module):
super(Lldp_interfaces, self).__init__(module)
def get_lldp_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset, self.gather_network_resources)
lldp_interfaces_facts = facts['ansible_network_resources'].get(
'lldp_interfaces')
if not lldp_interfaces_facts:
return []
return lldp_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
requests = list()
existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
requests.extend(self.set_config(existing_lldp_interfaces_facts))
if requests:
if not self._module.check_mode:
send_requests(self._module, requests=requests)
result['changed'] = True
result['requests'] = requests
changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
result['before'] = existing_lldp_interfaces_facts
if result['changed']:
result['after'] = changed_lldp_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_lldp_interfaces_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 requests necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_lldp_interfaces_facts
resp = self.set_state(want, have)
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 requests necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'overridden':
requests = self._state_overridden(want, have)
elif state == 'deleted':
requests = self._state_deleted(want, have)
elif state == 'merged':
requests = self._state_merged(want, have)
elif state == 'replaced':
requests = self._state_replaced(want, have)
return requests
def _state_replaced(self, want, have):
""" The request generator when state is replaced
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
for w in want:
for h in have:
if w['name'] == h['name']:
lldp_request = self._update_patch_request(w, h)
if lldp_request["path"]:
lldp_request["data"] = json.dumps(lldp_request["data"])
requests.append(lldp_request)
return requests
def _state_overridden(self, want, have):
""" The request generator when state is overridden
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
have_copy = []
for w in want:
for h in have:
if w['name'] == h['name']:
lldp_request = self._update_patch_request(w, h)
if lldp_request["path"]:
lldp_request["data"] = json.dumps(lldp_request["data"])
requests.append(lldp_request)
have_copy.append(h)
for h in have:
if h not in have_copy:
if not h['enabled']:
lldp_delete = self._update_delete_request(h)
if lldp_delete["path"]:
lldp_delete["data"] = json.dumps(lldp_delete["data"])
requests.append(lldp_delete)
return requests
def _state_merged(self, want, have):
""" The request generator when state is merged
:rtype: A list
:returns: the requests necessary to merge the provided into
the current configuration
"""
requests = []
for w in want:
for h in have:
if w['name'] == h['name']:
lldp_request = self._update_patch_request(w, h)
if lldp_request["path"]:
lldp_request["data"] = json.dumps(lldp_request["data"])
requests.append(lldp_request)
return requests
def _state_deleted(self, want, have):
""" The request generator when state is deleted
:rtype: A list
:returns: the requests necessary to remove the current configuration
of the provided objects
"""
requests = []
if want:
for w in want:
for h in have:
if w['name'] == h['name']:
if not h['enabled']:
lldp_delete = self._update_delete_request(h)
if lldp_delete["path"]:
lldp_delete["data"] = json.dumps(
lldp_delete["data"])
requests.append(lldp_delete)
else:
for h in have:
if not h['enabled']:
lldp_delete = self._update_delete_request(h)
if lldp_delete["path"]:
lldp_delete["data"] = json.dumps(lldp_delete["data"])
requests.append(lldp_delete)
return requests
def _update_patch_request(self, want, have):
lldp_request = deepcopy(self.LLDP_INTERFACE)
if have['enabled'] != want['enabled']:
lldp_request["data"]["openconfig-lldp:config"]["name"] = want[
'name']
lldp_request["data"]["openconfig-lldp:config"]["enabled"] = want[
'enabled']
lldp_request["path"] = self.LLDP_PATH + str(
want['name']) + "/config"
return lldp_request
def _update_delete_request(self, have):
lldp_delete = deepcopy(self.LLDP_INTERFACE)
lldp_delete["data"]["openconfig-lldp:config"]["name"] = have['name']
lldp_delete["data"]["openconfig-lldp:config"]["enabled"] = True
lldp_delete["path"] = self.LLDP_PATH + str(have['name']) + "/config"
return lldp_delete

View file

@ -0,0 +1,277 @@
#
# -*- 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 exos_vlans 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 __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ConfigBase
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, dict_diff
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.facts import Facts
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
from ansible_collections.community.general.plugins.module_utils.network.exos.utils.utils import search_obj_in_list
class Vlans(ConfigBase):
"""
The exos_vlans class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'vlans',
]
VLAN_POST = {
"data": {"openconfig-vlan:vlans": []},
"method": "POST",
"path": "/rest/restconf/data/openconfig-vlan:vlans/"
}
VLAN_PATCH = {
"data": {"openconfig-vlan:vlans": {"vlan": []}},
"method": "PATCH",
"path": "/rest/restconf/data/openconfig-vlan:vlans/"
}
VLAN_DELETE = {
"method": "DELETE",
"path": None
}
DEL_PATH = "/rest/restconf/data/openconfig-vlan:vlans/vlan="
REQUEST_BODY = {
"config": {"name": None, "status": "ACTIVE", "tpid": "oc-vlan-types:TPID_0x8100", "vlan-id": None}
}
def __init__(self, module):
super(Vlans, self).__init__(module)
def get_vlans_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset, self.gather_network_resources)
vlans_facts = facts['ansible_network_resources'].get('vlans')
if not vlans_facts:
return []
return vlans_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
requests = list()
existing_vlans_facts = self.get_vlans_facts()
requests.extend(self.set_config(existing_vlans_facts))
if requests:
if not self._module.check_mode:
send_requests(self._module, requests=requests)
result['changed'] = True
result['requests'] = requests
changed_vlans_facts = self.get_vlans_facts()
result['before'] = existing_vlans_facts
if result['changed']:
result['after'] = changed_vlans_facts
result['warnings'] = warnings
return result
def set_config(self, existing_vlans_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 requests necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_vlans_facts
resp = self.set_state(want, have)
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 requests necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state == 'overridden':
requests = self._state_overridden(want, have)
elif state == 'deleted':
requests = self._state_deleted(want, have)
elif state == 'merged':
requests = self._state_merged(want, have)
elif state == 'replaced':
requests = self._state_replaced(want, have)
return requests
def _state_replaced(self, want, have):
""" The request generator when state is replaced
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
request_patch = deepcopy(self.VLAN_PATCH)
for w in want:
if w.get('vlan_id'):
h = search_obj_in_list(w['vlan_id'], have, 'vlan_id')
if h:
if dict_diff(w, h):
request_body = self._update_patch_request(w)
request_patch["data"]["openconfig-vlan:vlans"]["vlan"].append(request_body)
else:
request_post = self._update_post_request(w)
requests.append(request_post)
if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]):
request_patch["data"] = json.dumps(request_patch["data"])
requests.append(request_patch)
return requests
def _state_overridden(self, want, have):
""" The request generator when state is overridden
:rtype: A list
:returns: the requests necessary to migrate the current configuration
to the desired configuration
"""
requests = []
request_patch = deepcopy(self.VLAN_PATCH)
have_copy = []
for w in want:
if w.get('vlan_id'):
h = search_obj_in_list(w['vlan_id'], have, 'vlan_id')
if h:
if dict_diff(w, h):
request_body = self._update_patch_request(w)
request_patch["data"]["openconfig-vlan:vlans"]["vlan"].append(request_body)
have_copy.append(h)
else:
request_post = self._update_post_request(w)
requests.append(request_post)
for h in have:
if h not in have_copy and h['vlan_id'] != 1:
request_delete = self._update_delete_request(h)
requests.append(request_delete)
if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]):
request_patch["data"] = json.dumps(request_patch["data"])
requests.append(request_patch)
return requests
def _state_merged(self, want, have):
""" The requests generator when state is merged
:rtype: A list
:returns: the requests necessary to merge the provided into
the current configuration
"""
requests = []
request_patch = deepcopy(self.VLAN_PATCH)
for w in want:
if w.get('vlan_id'):
h = search_obj_in_list(w['vlan_id'], have, 'vlan_id')
if h:
if dict_diff(w, h):
request_body = self._update_patch_request(w)
request_patch["data"]["openconfig-vlan:vlans"]["vlan"].append(request_body)
else:
request_post = self._update_post_request(w)
requests.append(request_post)
if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]):
request_patch["data"] = json.dumps(request_patch["data"])
requests.append(request_patch)
return requests
def _state_deleted(self, want, have):
""" The requests generator when state is deleted
:rtype: A list
:returns: the requests necessary to remove the current configuration
of the provided objects
"""
requests = []
if want:
for w in want:
if w.get('vlan_id'):
h = search_obj_in_list(w['vlan_id'], have, 'vlan_id')
if h:
request_delete = self._update_delete_request(h)
requests.append(request_delete)
else:
if not have:
return requests
for h in have:
if h['vlan_id'] == 1:
continue
else:
request_delete = self._update_delete_request(h)
requests.append(request_delete)
return requests
def _update_vlan_config_body(self, want, request):
request["config"]["name"] = want["name"]
request["config"]["status"] = "SUSPENDED" if want["state"] == "suspend" else want["state"].upper()
request["config"]["vlan-id"] = want["vlan_id"]
return request
def _update_patch_request(self, want):
request_body = deepcopy(self.REQUEST_BODY)
request_body = self._update_vlan_config_body(want, request_body)
return request_body
def _update_post_request(self, want):
request_post = deepcopy(self.VLAN_POST)
request_body = deepcopy(self.REQUEST_BODY)
request_body = self._update_vlan_config_body(want, request_body)
request_post["data"]["openconfig-vlan:vlans"].append(request_body)
request_post["data"] = json.dumps(request_post["data"])
return request_post
def _update_delete_request(self, have):
request_delete = deepcopy(self.VLAN_DELETE)
request_delete["path"] = self.DEL_PATH + str(have['vlan_id'])
return request_delete

View file

@ -0,0 +1,219 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# (c) 2016 Red Hat Inc.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils.connection import Connection, ConnectionError
_DEVICE_CONNECTION = None
class Cli:
def __init__(self, module):
self._module = module
self._device_configs = {}
self._connection = None
def get_capabilities(self):
"""Returns platform info of the remove device
"""
connection = self._get_connection()
return json.loads(connection.get_capabilities())
def _get_connection(self):
if not self._connection:
self._connection = Connection(self._module._socket_path)
return self._connection
def get_config(self, flags=None):
"""Retrieves the current config from the device or cache
"""
flags = [] if flags is None else flags
if self._device_configs == {}:
connection = self._get_connection()
try:
out = connection.get_config(flags=flags)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
self._device_configs = to_text(out, errors='surrogate_then_replace').strip()
return self._device_configs
def run_commands(self, commands, check_rc=True):
"""Runs list of commands on remote device and returns results
"""
connection = self._get_connection()
try:
response = connection.run_commands(commands=commands, check_rc=check_rc)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return response
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
conn = self._get_connection()
try:
diff = conn.get_diff(candidate=candidate, running=running, diff_match=diff_match,
diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return diff
class HttpApi:
def __init__(self, module):
self._module = module
self._device_configs = {}
self._connection_obj = None
def get_capabilities(self):
"""Returns platform info of the remove device
"""
try:
capabilities = self._connection.get_capabilities()
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return json.loads(capabilities)
@property
def _connection(self):
if not self._connection_obj:
self._connection_obj = Connection(self._module._socket_path)
return self._connection_obj
def get_config(self, flags=None):
"""Retrieves the current config from the device or cache
"""
flags = [] if flags is None else flags
if self._device_configs == {}:
try:
out = self._connection.get_config(flags=flags)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
self._device_configs = to_text(out, errors='surrogate_then_replace').strip()
return self._device_configs
def run_commands(self, commands, check_rc=True):
"""Runs list of commands on remote device and returns results
"""
try:
response = self._connection.run_commands(commands=commands, check_rc=check_rc)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return response
def send_requests(self, requests):
"""Send a list of http requests to remote device and return results
"""
if requests is None:
raise ValueError("'requests' value is required")
responses = list()
for req in to_list(requests):
try:
response = self._connection.send_request(**req)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
responses.append(response)
return responses
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
try:
diff = self._connection.get_diff(candidate=candidate, running=running, diff_match=diff_match,
diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace)
except ConnectionError as exc:
self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return diff
def get_capabilities(module):
conn = get_connection(module)
return conn.get_capabilities()
def get_connection(module):
global _DEVICE_CONNECTION
if not _DEVICE_CONNECTION:
connection_proxy = Connection(module._socket_path)
cap = json.loads(connection_proxy.get_capabilities())
if cap['network_api'] == 'cliconf':
conn = Cli(module)
elif cap['network_api'] == 'exosapi':
conn = HttpApi(module)
else:
module.fail_json(msg='Invalid connection type %s' % cap['network_api'])
_DEVICE_CONNECTION = conn
return _DEVICE_CONNECTION
def get_config(module, flags=None):
flags = None if flags is None else flags
conn = get_connection(module)
return conn.get_config(flags)
def load_config(module, commands):
conn = get_connection(module)
return conn.run_commands(to_command(module, commands))
def run_commands(module, commands, check_rc=True):
conn = get_connection(module)
return conn.run_commands(to_command(module, commands), check_rc=check_rc)
def to_command(module, commands):
transform = ComplexList(dict(
command=dict(key=True),
output=dict(default='text'),
prompt=dict(type='list'),
answer=dict(type='list'),
sendonly=dict(type='bool', default=False),
check_all=dict(type='bool', default=False),
), module)
return transform(to_list(commands))
def send_requests(module, requests):
conn = get_connection(module)
return conn.send_requests(to_request(module, requests))
def to_request(module, requests):
transform = ComplexList(dict(
path=dict(key=True),
method=dict(),
data=dict(type='dict'),
), module)
return transform(to_list(requests))
def get_diff(module, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
conn = get_connection(module)
return conn.get_diff(candidate=candidate, running=running, diff_match=diff_match, diff_ignore_lines=diff_ignore_lines, path=path, diff_replace=diff_replace)

View file

@ -0,0 +1,61 @@
#
# -*- 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 exos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.community.general.plugins.module_utils.network.exos.argspec.facts.facts import FactsArgs
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import FactsBase
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.vlans.vlans import VlansFacts
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.legacy.base import Default, Hardware, Interfaces, Config
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible_collections.community.general.plugins.module_utils.network.exos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
FACT_LEGACY_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config)
FACT_RESOURCE_SUBSETS = dict(
lldp_global=Lldp_globalFacts,
vlans=VlansFacts,
lldp_interfaces=Lldp_interfacesFacts,
l2_interfaces=L2_interfacesFacts,
)
class Facts(FactsBase):
""" The fact class for exos
"""
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 exos
: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
"""
if self.VALID_RESOURCE_SUBSETS:
self.get_network_resources_facts(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

View file

@ -0,0 +1,92 @@
#
# -*- 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 exos l2_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.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.community.general.plugins.module_utils.network.exos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
class L2_interfacesFacts(object):
""" The exos l2_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = L2_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 l2_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
request = [{
"path": "/rest/restconf/data/openconfig-interfaces:interfaces",
"method": "GET"
}]
data = send_requests(self._module, requests=request)
objs = []
if data:
for d in data[0]["openconfig-interfaces:interfaces"]["interface"]:
obj = self.render_config(self.generated_spec, d)
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('l2_interfaces', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['l2_interfaces'] = params['config']
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)
if conf["config"]["type"] == "ethernetCsmacd":
conf_dict = conf["openconfig-if-ethernet:ethernet"]["openconfig-vlan:switched-vlan"]["config"]
config["name"] = conf["name"]
if conf_dict["interface-mode"] == "ACCESS":
config["access"]["vlan"] = conf_dict.get("access-vlan")
else:
if 'native-vlan' in conf_dict:
config["trunk"]["native_vlan"] = conf_dict.get("native-vlan")
config["trunk"]["trunk_allowed_vlans"] = conf_dict.get("trunk-vlans")
return utils.remove_empties(config)

View file

@ -0,0 +1,263 @@
# -*- 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 exos legacy 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 __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
import json
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import run_commands
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
class FactsBase(object):
COMMANDS = list()
def __init__(self, module):
self.module = module
self.facts = dict()
self.warnings = list()
self.responses = None
def populate(self):
self.responses = run_commands(self.module, self.COMMANDS)
def run(self, cmd):
return run_commands(self.module, cmd)
class Default(FactsBase):
COMMANDS = [
'show version',
'show switch'
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
if data:
self.facts['version'] = self.parse_version(data)
self.facts['serialnum'] = self.parse_serialnum(data)
data = self.responses[1]
if data:
self.facts['model'] = self.parse_model(data)
self.facts['hostname'] = self.parse_hostname(data)
def parse_version(self, data):
match = re.search(r'Image\s+: ExtremeXOS version (\S+)', data)
if match:
return match.group(1)
def parse_model(self, data):
match = re.search(r'System Type:\s+(.*$)', data, re.M)
if match:
return match.group(1)
def parse_hostname(self, data):
match = re.search(r'SysName:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def parse_serialnum(self, data):
match = re.search(r'Switch\s+: \S+ (\S+)', data, re.M)
if match:
return match.group(1)
# For stack, return serial number of the first switch in the stack.
match = re.search(r'Slot-\d+\s+: \S+ (\S+)', data, re.M)
if match:
return match.group(1)
# Handle unique formatting for VM
match = re.search(r'Switch\s+: PN:\S+\s+SN:(\S+)', data, re.M)
if match:
return match.group(1)
class Hardware(FactsBase):
COMMANDS = [
'show memory'
]
def populate(self):
super(Hardware, self).populate()
data = self.responses[0]
if data:
self.facts['memtotal_mb'] = int(round(int(self.parse_memtotal(data)) / 1024, 0))
self.facts['memfree_mb'] = int(round(int(self.parse_memfree(data)) / 1024, 0))
def parse_memtotal(self, data):
match = re.search(r' Total DRAM \(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
# Handle unique formatting for VM
match = re.search(r' Total \s+\(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
def parse_memfree(self, data):
match = re.search(r' Free\s+\(KB\): (\d+)', data, re.M)
if match:
return match.group(1)
class Config(FactsBase):
COMMANDS = ['show configuration detail']
def populate(self):
super(Config, self).populate()
data = self.responses[0]
if data:
self.facts['config'] = data
class Interfaces(FactsBase):
COMMANDS = [
'show switch',
{'command': 'show port config', 'output': 'json'},
{'command': 'show port description', 'output': 'json'},
{'command': 'show vlan detail', 'output': 'json'},
{'command': 'show lldp neighbors', 'output': 'json'}
]
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data = self.responses[0]
if data:
sysmac = self.parse_sysmac(data)
data = self.responses[1]
if data:
self.facts['interfaces'] = self.populate_interfaces(data, sysmac)
data = self.responses[2]
if data:
self.populate_interface_descriptions(data)
data = self.responses[3]
if data:
self.populate_vlan_interfaces(data, sysmac)
data = self.responses[4]
if data:
self.facts['neighbors'] = self.parse_neighbors(data)
def parse_sysmac(self, data):
match = re.search(r'System MAC:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def populate_interfaces(self, interfaces, sysmac):
facts = dict()
for elem in interfaces:
intf = dict()
if 'show_ports_config' not in elem:
continue
key = str(elem['show_ports_config']['port'])
if elem['show_ports_config']['linkState'] == 2:
# Link state is "not present", don't include
continue
intf['type'] = 'Ethernet'
intf['macaddress'] = sysmac
intf['bandwidth_configured'] = str(elem['show_ports_config']['speedCfg'])
intf['bandwidth'] = str(elem['show_ports_config']['speedActual'])
intf['duplex_configured'] = elem['show_ports_config']['duplexCfg']
intf['duplex'] = elem['show_ports_config']['duplexActual']
if elem['show_ports_config']['linkState'] == 1:
intf['lineprotocol'] = 'up'
else:
intf['lineprotocol'] = 'down'
if elem['show_ports_config']['portState'] == 1:
intf['operstatus'] = 'up'
else:
intf['operstatus'] = 'admin down'
facts[key] = intf
return facts
def populate_interface_descriptions(self, data):
for elem in data:
if 'show_ports_description' not in elem:
continue
key = str(elem['show_ports_description']['port'])
if 'descriptionString' in elem['show_ports_description']:
desc = elem['show_ports_description']['descriptionString']
self.facts['interfaces'][key]['description'] = desc
def populate_vlan_interfaces(self, data, sysmac):
for elem in data:
if 'vlanProc' in elem:
key = elem['vlanProc']['name1']
if key not in self.facts['interfaces']:
intf = dict()
intf['type'] = 'VLAN'
intf['macaddress'] = sysmac
self.facts['interfaces'][key] = intf
if elem['vlanProc']['ipAddress'] != '0.0.0.0':
self.facts['interfaces'][key]['ipv4'] = list()
addr = elem['vlanProc']['ipAddress']
subnet = elem['vlanProc']['maskForDisplay']
ipv4 = dict(address=addr, subnet=subnet)
self.add_ip_address(addr, 'ipv4')
self.facts['interfaces'][key]['ipv4'].append(ipv4)
if 'rtifIpv6Address' in elem:
key = elem['rtifIpv6Address']['rtifName']
if key not in self.facts['interfaces']:
intf = dict()
intf['type'] = 'VLAN'
intf['macaddress'] = sysmac
self.facts['interfaces'][key] = intf
self.facts['interfaces'][key]['ipv6'] = list()
addr, subnet = elem['rtifIpv6Address']['ipv6_address_mask'].split('/')
ipv6 = dict(address=addr, subnet=subnet)
self.add_ip_address(addr, 'ipv6')
self.facts['interfaces'][key]['ipv6'].append(ipv6)
def add_ip_address(self, address, family):
if family == 'ipv4':
if address not in self.facts['all_ipv4_addresses']:
self.facts['all_ipv4_addresses'].append(address)
else:
if address not in self.facts['all_ipv6_addresses']:
self.facts['all_ipv6_addresses'].append(address)
def parse_neighbors(self, data):
facts = dict()
for elem in data:
if 'lldpPortNbrInfoShort' not in elem:
continue
intf = str(elem['lldpPortNbrInfoShort']['port'])
if intf not in facts:
facts[intf] = list()
fact = dict()
fact['host'] = elem['lldpPortNbrInfoShort']['nbrSysName']
fact['port'] = str(elem['lldpPortNbrInfoShort']['nbrPortID'])
facts[intf].append(fact)
return facts

View file

@ -0,0 +1,97 @@
#
# -*- 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 exos lldp_global 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 __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.community.general.plugins.module_utils.network.exos.argspec.lldp_global.lldp_global \
import Lldp_globalArgs
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
class Lldp_globalFacts(object):
""" The exos lldp_global fact class
"""
TLV_SELECT_OPTIONS = [
"SYSTEM_NAME",
"SYSTEM_DESCRIPTION",
"SYSTEM_CAPABILITIES",
"MANAGEMENT_ADDRESS",
"PORT_DESCRIPTION"]
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Lldp_globalArgs.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 lldp_global
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
request = {
"path": "/rest/restconf/data/openconfig-lldp:lldp/config/",
"method": "GET",
}
data = send_requests(self._module, request)
obj = {}
if data:
lldp_obj = self.render_config(self.generated_spec, data[0])
if lldp_obj:
obj = lldp_obj
ansible_facts['ansible_network_resources'].pop('lldp_global', None)
facts = {}
params = utils.validate_config(self.argument_spec, {'config': obj})
facts['lldp_global'] = params['config']
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)
config['interval'] = conf["openconfig-lldp:config"]["hello-timer"]
for item in self.TLV_SELECT_OPTIONS:
config["tlv_select"][item.lower()] = (
False if (item in conf["openconfig-lldp:config"]["suppress-tlv-advertisement"])
else True)
return utils.remove_empties(config)

View file

@ -0,0 +1,88 @@
#
# -*- 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 exos lldp_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.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.community.general.plugins.module_utils.network.exos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
class Lldp_interfacesFacts(object):
""" The exos lldp_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Lldp_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 lldp_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
request = [{
"path": "/rest/restconf/data/openconfig-lldp:lldp/interfaces?depth=4",
"method": "GET"
}]
data = send_requests(self._module, requests=request)
objs = []
if data:
for d in data[0]["openconfig-lldp:interfaces"]["interface"]:
obj = self.render_config(self.generated_spec, d["config"])
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('lldp_interfaces', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['lldp_interfaces'] = params['config']
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)
config["name"] = conf["name"]
config["enabled"] = bool(conf["enabled"])
return utils.remove_empties(config)

View file

@ -0,0 +1,89 @@
#
# -*- 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 exos 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 __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.community.general.plugins.module_utils.network.exos.argspec.vlans.vlans import VlansArgs
from ansible_collections.community.general.plugins.module_utils.network.exos.exos import send_requests
class VlansFacts(object):
""" The exos vlans fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = VlansArgs.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 not data:
request = [{
"path": "/rest/restconf/data/openconfig-vlan:vlans?depth=5",
"method": "GET"
}]
data = send_requests(self._module, requests=request)
objs = []
if data:
for d in data[0]["openconfig-vlan:vlans"]["vlan"]:
obj = self.render_config(self.generated_spec, d["config"])
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('vlans', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['vlans'] = params['config']
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)
config["name"] = conf["name"]
config["state"] = "suspend" if conf["status"] == "SUSPENDED" else conf["status"].lower()
config["vlan_id"] = conf["vlan-id"]
return utils.remove_empties(config)

View file

@ -0,0 +1,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
def search_obj_in_list(item, lst, key):
for o in lst:
if o[key] == item:
return o
return None