Mellanox OS name change: MLNXOS changed to ONYX (#34753)

* Mellanox OS name change: MLNXOS changed to ONYX

Signed-off-by: Samer Deeb <samerd@mellanox.com>

* Fix alphabetical order of modules metadata

Signed-off-by: Samer Deeb <samerd@mellanox.com>
This commit is contained in:
Samer Deeb
2018-01-11 14:21:17 -08:00
committed by John R Barker
parent 57ff84251e
commit f8884f12bc
73 changed files with 381 additions and 383 deletions

View File

@@ -0,0 +1,256 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_bgp
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Configures BGP on Mellanox ONYX network devices
description:
- This module provides declarative management of BGP router and neighbors
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
as_number:
description:
- Local AS number.
required: true
router_id:
description:
- Router IP address. Required if I(state=present).
neighbors:
description:
- List of neighbors. Required if I(state=present).
suboptions:
remote_as:
description:
- Remote AS number.
required: true
neighbor:
description:
- Neighbor IP address.
required: true
networks:
description:
- List of advertised networks.
state:
description:
- BGP state.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: configure bgp
onyx_bgp:
as_number: 320
router_id: 10.3.3.3
neighbors:
- remote_as: 321
neighbor: 10.3.3.4
state: present
networks:
- 172.16.1.0/24
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- router bgp 172
- exit
- router bgp 172 router-id 2.3.4.5 force
- router bgp 172 neighbor 2.3.4.6 remote-as 173
- router bgp 172 network 172.16.1.0 /24
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.onyx.onyx import get_bgp_summary
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxBgpModule(BaseOnyxModule):
LOCAL_AS_REGEX = re.compile(r'^\s+router bgp\s+(\d+).*')
ROUTER_ID_REGEX = re.compile(
r'^\s+router bgp\s+(\d+).*router-id\s+(\S+)\s+.*')
NEIGHBOR_REGEX = re.compile(
r'^\s+router bgp\s+(\d+).*neighbor\s+(\S+)\s+remote\-as\s+(\S+).*')
NETWORK_REGEX = re.compile(
r'^\s+router bgp\s+(\d+).*network\s+(\S+)\s+(\S+).*')
def init_module(self):
""" initialize module
"""
neighbor_spec = dict(
remote_as=dict(type='int', required=True),
neighbor=dict(required=True),
)
element_spec = dict(
as_number=dict(type='int', required=True),
router_id=dict(),
neighbors=dict(type='list', elements='dict',
options=neighbor_spec),
networks=dict(type='list', elements='str'),
state=dict(choices=['present', 'absent'], default='present'),
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def get_required_config(self):
module_params = self._module.params
req_neighbors = list()
self._required_config = dict(
as_number=module_params['as_number'],
router_id=module_params['router_id'],
state=module_params['state'],
neighbors=req_neighbors,
networks=module_params['networks'])
neighbors = module_params['neighbors'] or list()
for neighbor_data in neighbors:
req_neighbors.append(
(neighbor_data['neighbor'], neighbor_data['remote_as']))
self.validate_param_values(self._required_config)
def _set_bgp_config(self, bgp_config):
lines = bgp_config.split('\n')
self._current_config['router_id'] = None
self._current_config['as_number'] = None
neighbors = self._current_config['neighbors'] = []
networks = self._current_config['networks'] = []
for line in lines:
if line.startswith('#'):
continue
if not self._current_config['as_number']:
match = self.LOCAL_AS_REGEX.match(line)
if match:
self._current_config['as_number'] = int(match.group(1))
continue
if not self._current_config['router_id']:
match = self.ROUTER_ID_REGEX.match(line)
if match:
self._current_config['router_id'] = match.group(2)
continue
match = self.NEIGHBOR_REGEX.match(line)
if match:
neighbors.append((match.group(2), int(match.group(3))))
continue
match = self.NETWORK_REGEX.match(line)
if match:
network = match.group(2) + match.group(3)
networks.append(network)
continue
def _get_bgp_summary(self):
return get_bgp_summary(self._module)
def load_current_config(self):
self._current_config = dict()
bgp_config = self._get_bgp_summary()
if bgp_config:
self._set_bgp_config(bgp_config)
def generate_commands(self):
state = self._required_config['state']
if state == 'present':
self._generate_bgp_cmds()
else:
self._generate_no_bgp_cmds()
def _generate_bgp_cmds(self):
as_number = self._required_config['as_number']
curr_as_num = self._current_config.get('as_number')
bgp_removed = False
if curr_as_num != as_number:
if curr_as_num:
self._commands.append('no router bgp %d' % curr_as_num)
bgp_removed = True
self._commands.append('router bgp %d' % as_number)
self._commands.append('exit')
curr_route_id = self._current_config.get('router_id')
req_router_id = self._required_config['router_id']
if req_router_id and req_router_id != curr_route_id or bgp_removed:
self._commands.append('router bgp %d router-id %s force' %
(as_number, req_router_id))
self._generate_neighbors_cmds(as_number, bgp_removed)
self._generate_networks_cmds(as_number, bgp_removed)
def _generate_neighbors_cmds(self, as_number, bgp_removed):
req_neighbors = self._required_config['neighbors']
curr_neighbors = self._current_config.get('neighbors', [])
if not bgp_removed:
for neighbor_data in curr_neighbors:
if neighbor_data not in req_neighbors:
(neighbor, remote_as) = neighbor_data
self._commands.append(
'router bgp %s no neighbor %s remote-as %s' %
(as_number, neighbor, remote_as))
for neighbor_data in req_neighbors:
if bgp_removed or neighbor_data not in curr_neighbors:
(neighbor, remote_as) = neighbor_data
self._commands.append(
'router bgp %s neighbor %s remote-as %s' %
(as_number, neighbor, remote_as))
def _generate_networks_cmds(self, as_number, bgp_removed):
req_networks = self._required_config['networks'] or []
curr_networks = self._current_config.get('networks', [])
if not bgp_removed:
for network in curr_networks:
if network not in req_networks:
net_attrs = network.split('/')
if len(net_attrs) != 2:
self._module.fail_json(
msg='Invalid network %s' % network)
net_address, netmask = net_attrs
cmd = 'router bgp %s no network %s /%s' % (
as_number, net_address, netmask)
self._commands.append(cmd)
for network in req_networks:
if bgp_removed or network not in curr_networks:
net_attrs = network.split('/')
if len(net_attrs) != 2:
self._module.fail_json(
msg='Invalid network %s' % network)
net_address, netmask = net_attrs
cmd = 'router bgp %s network %s /%s' % (
as_number, net_address, netmask)
self._commands.append(cmd)
def _generate_no_bgp_cmds(self):
as_number = self._required_config['as_number']
curr_as_num = self._current_config.get('as_number')
if curr_as_num and curr_as_num == as_number:
self._commands.append('no router bgp %d' % as_number)
def main():
""" main entry point for module execution
"""
OnyxBgpModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,215 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_command
extends_documentation_fragment: onyx
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Run commands on remote devices running Mellanox ONYX
description:
- Sends arbitrary commands to an Mellanox ONYX network device and returns
the results read from the device. This module includes an
argument that will cause the module to wait for a specific condition
before returning or timing out if the condition is not met.
- This module does not support running commands in configuration mode.
Please use M(onyx_config) to configure Mellanox ONYX devices.
notes:
- Tested on ONYX 3.6.4000
options:
commands:
description:
- List of commands to send to the remote Mellanox ONYX network device.
The resulting output from the command
is returned. If the I(wait_for) argument is provided, the
module is not returned until the condition is satisfied or
the number of retries has expired.
required: true
wait_for:
description:
- List of conditions to evaluate against the output of the
command. The task will wait for each condition to be true
before moving forward. If the conditional is not true
within the configured number of retries, the task fails.
See examples.
match:
description:
- The I(match) argument is used in conjunction with the
I(wait_for) argument to specify the match policy. Valid
values are C(all) or C(any). If the value is set to C(all)
then all conditionals in the wait_for must be satisfied. If
the value is set to C(any) then only one of the values must be
satisfied.
default: all
choices: ['any', 'all']
retries:
description:
- Specifies the number of retries a command should by tried
before it is considered failed. The command is run on the
target device every retry and evaluated against the
I(wait_for) conditions.
default: 10
interval:
description:
- Configures the interval in seconds to wait between retries
of the command. If the command does not pass the specified
conditions, the interval indicates how long to wait before
trying the command again.
default: 1
"""
EXAMPLES = """
tasks:
- name: run show version on remote devices
onyx_command:
commands: show version
- name: run show version and check to see if output contains MLNXOS
onyx_command:
commands: show version
wait_for: result[0] contains MLNXOS
- name: run multiple commands on remote nodes
onyx_command:
commands:
- show version
- show interfaces
- name: run multiple commands and evaluate the output
onyx_command:
commands:
- show version
- show interfaces
wait_for:
- result[0] contains MLNXOS
- result[1] contains mgmt1
"""
RETURN = """
stdout:
description: The set of responses from the commands
returned: always apart from low level errors (such as action plugin)
type: list
sample: ['...', '...']
stdout_lines:
description: The value of stdout split into a list
returned: always apart from low level errors (such as action plugin)
type: list
sample: [['...', '...'], ['...'], ['...']]
failed_conditions:
description: The list of conditionals that have failed
returned: failed
type: list
sample: ['...', '...']
"""
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.parsing import Conditional
from ansible.module_utils.network.common.utils import ComplexList
from ansible.module_utils.six import string_types
from ansible.module_utils.network.onyx.onyx import run_commands
def to_lines(stdout):
for item in stdout:
if isinstance(item, string_types):
item = str(item).split('\n')
yield item
def parse_commands(module, warnings):
command = ComplexList(dict(
command=dict(key=True),
prompt=dict(),
answer=dict()
), module)
commands = command(module.params['commands'])
for item in list(commands):
if module.check_mode and not item['command'].startswith('show'):
warnings.append(
'only show commands are supported when using check mode, not '
'executing `%s`' % item['command']
)
commands.remove(item)
elif item['command'].startswith('conf'):
module.fail_json(
msg='onyx_command does not support running config mode '
'commands. Please use onyx_config instead'
)
return commands
def main():
"""main entry point for module execution
"""
argument_spec = dict(
commands=dict(type='list', required=True),
wait_for=dict(type='list'),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
result = {'changed': False}
warnings = list()
commands = parse_commands(module, warnings)
result['warnings'] = warnings
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
while retries > 0:
responses = run_commands(module, commands)
for item in list(conditionals):
if item(responses):
if match == 'any':
conditionals = list()
break
conditionals.remove(item)
if not conditionals:
break
time.sleep(interval)
retries -= 1
if conditionals:
failed_conditions = [item.raw for item in conditionals]
msg = 'One or more conditional statements have not be satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update({
'changed': False,
'stdout': responses,
'stdout_lines': list(to_lines(responses))
})
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,229 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_config
extends_documentation_fragment: onyx
version_added: "2.5"
author: "Alex Tabachnik (@atabachnik), Samer Deeb (@samerd)"
short_description: Manage Mellanox ONYX configuration sections
description:
- Mellanox ONYX configurations uses a simple block indent file syntax
for segmenting configuration into sections. This module provides
an implementation for working with ONYX configuration sections in
a deterministic way.
options:
lines:
description:
- The ordered set of commands that should be configured in the
section. The commands must be the exact same commands as found
in the device running-config. Be sure to note the configuration
command syntax as some commands are automatically modified by the
device config parser.
aliases: ['commands']
parents:
description:
- The ordered set of parents that uniquely identify the section
the commands should be checked against. If the parents argument
is omitted, the commands are checked against the set of top
level or global commands.
src:
description:
- Specifies the source path to the file that contains the configuration
or configuration template to load. The path to the source file can
either be the full path on the Ansible control host or a relative
path from the playbook or role root directory. This argument is mutually
exclusive with I(lines), I(parents).
before:
description:
- The ordered set of commands to push on to the command stack if
a change needs to be made. This allows the playbook designer
the opportunity to perform configuration commands prior to pushing
any changes without affecting how the set of commands are matched
against the system.
after:
description:
- The ordered set of commands to append to the end of the command
stack if a change needs to be made. Just like with I(before) this
allows the playbook designer to append a set of commands to be
executed after the command set.
match:
description:
- Instructs the module on the way to perform the matching of
the set of commands against the current device config. If
match is set to I(line), commands are matched line by line. If
match is set to I(strict), command lines are matched with respect
to position. If match is set to I(exact), command lines
must be an equal match. Finally, if match is set to I(none), the
module will not attempt to compare the source configuration with
the running configuration on the remote device.
default: line
choices: ['line', 'strict', 'exact', 'none']
replace:
description:
- Instructs the module on the way to perform the configuration
on the device. If the replace argument is set to I(line) then
the modified lines are pushed to the device in configuration
mode. If the replace argument is set to I(block) then the entire
command block is pushed to the device in configuration mode if any
line is not correct
default: line
choices: ['line', 'block']
backup:
description:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
folder in the playbook root directory. If the directory does not
exist, it is created.
default: no
choices: ['yes', 'no']
config:
description:
- The C(config) argument allows the playbook designer to supply
the base configuration to be used to validate configuration
changes necessary. If this argument is provided, the module
will not download the running-config from the remote node.
save:
description:
- The C(save) argument instructs the module to save the running-
config to the startup-config at the conclusion of the module
running. If check mode is specified, this argument is ignored.
default: no
choices: ['yes', 'no']
"""
EXAMPLES = """
---
- onyx_config:
lines:
- snmp-server community
- snmp-server host 10.2.2.2 traps version 2c
"""
RETURN = """
updates:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['...', '...']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: string
sample: /playbooks/ansible/backup/onyx_config.2016-07-16@22:28:34
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import NetworkConfig, dumps
from ansible.module_utils.network.onyx.onyx import get_config
from ansible.module_utils.network.onyx.onyx import load_config
from ansible.module_utils.network.onyx.onyx import run_commands
def get_candidate(module):
candidate = NetworkConfig(indent=1)
if module.params['src']:
candidate.load(module.params['src'])
elif module.params['lines']:
parents = module.params['parents'] or list()
candidate.add(module.params['lines'], parents=parents)
return candidate
def run(module, result):
match = module.params['match']
replace = module.params['replace']
path = module.params['parents']
candidate = get_candidate(module)
if match != 'none':
contents = module.params['config']
if not contents:
contents = get_config(module)
config = NetworkConfig(indent=1, contents=contents)
configobjs = candidate.difference(config, path=path, match=match,
replace=replace)
else:
configobjs = candidate.items
if configobjs:
commands = dumps(configobjs, 'commands').split('\n')
if module.params['lines']:
if module.params['before']:
commands[:0] = module.params['before']
if module.params['after']:
commands.extend(module.params['after'])
result['updates'] = commands
# send the configuration commands to the device and merge
# them with the current running config
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
if module.params['save']:
if not module.check_mode:
run_commands(module, 'configuration write')
result['changed'] = True
def main():
""" main entry point for module execution
"""
argument_spec = dict(
src=dict(type='path'),
lines=dict(aliases=['commands'], type='list'),
parents=dict(type='list'),
before=dict(type='list'),
after=dict(type='list'),
match=dict(default='line', choices=['line', 'strict', 'exact', 'none']),
replace=dict(default='line', choices=['line', 'block']),
config=dict(),
backup=dict(type='bool', default=False),
save=dict(type='bool', default=False),
)
mutually_exclusive = [('lines', 'src'),
('parents', 'src')]
required_if = [('match', 'strict', ['lines']),
('match', 'exact', ['lines']),
('replace', 'block', ['lines'])]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True)
result = {'changed': False}
if module.params['backup']:
result['__backup__'] = get_config(module)
run(module, result)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,238 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_facts
version_added: "2.5"
author: "Waleed Mousa (@waleedym), Samer Deeb (@samerd)"
short_description: Collect facts from Mellanox ONYX network devices
description:
- Collects a base set of device facts from a ONYX Mellanox network devices
This module prepends all of the base network fact keys with
C(ansible_net_<fact>). The facts module will always collect a base set of
facts from the device and can enable or disable collection of additional
facts.
notes:
- Tested against ONYX 3.6
options:
gather_subset:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
all, version, module, and 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: version
"""
EXAMPLES = """
---
- name: Collect all facts from the device
onyx_facts:
gather_subset: all
- name: Collect only the interfaces facts
onyx_facts:
gather_subset:
- interfaces
- name: Do not collect version facts
onyx_facts:
gather_subset:
- "!version"
"""
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device
returned: always
type: list
# version
ansible_net_version:
description: A hash of all curently running system image information
returned: when version is configured or when no gather_subset is provided
type: dict
# modules
ansible_net_modules:
description: A hash of all modules on the systeme with status
returned: when modules is configured
type: dict
# interfaces
ansible_net_interfaces:
description: A hash of all interfaces running on the system
returned: when interfaces is configured
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxFactsModule(BaseOnyxModule):
def get_runable_subset(self, gather_subset):
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:
self._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)
if not runable_subsets:
runable_subsets.add('version')
return runable_subsets
def init_module(self):
""" module intialization
"""
argument_spec = dict(
gather_subset=dict(default=['version'], type='list')
)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def run(self):
self.init_module()
gather_subset = self._module.params['gather_subset']
runable_subsets = self.get_runable_subset(gather_subset)
facts = dict()
facts['gather_subset'] = list(runable_subsets)
instances = list()
for key in runable_subsets:
facter_cls = FACT_SUBSETS[key]
instances.append(facter_cls(self._module))
for inst in instances:
inst.populate()
facts.update(inst.facts)
ansible_facts = dict()
for key, value in iteritems(facts):
key = 'ansible_net_%s' % key
ansible_facts[key] = value
self._module.exit_json(ansible_facts=ansible_facts)
class FactsBase(object):
COMMANDS = ['']
def __init__(self, module):
self.module = module
self.facts = dict()
self.responses = None
def _show_cmd(self, cmd):
return show_cmd(self.module, cmd, json_fmt=True)
def populate(self):
self.responses = []
for cmd in self.COMMANDS:
self.responses.append(self._show_cmd(cmd))
class Version(FactsBase):
COMMANDS = ['show version']
def populate(self):
super(Version, self).populate()
data = self.responses[0]
if data:
self.facts['version'] = data
class Module(FactsBase):
COMMANDS = ['show module']
def populate(self):
super(Module, self).populate()
data = self.responses[0]
if data:
self.facts['modules'] = data
class Interfaces(FactsBase):
COMMANDS = ['show interfaces ethernet']
def populate(self):
super(Interfaces, self).populate()
data = self.responses[0]
if data:
self.facts['interfaces'] = self.populate_interfaces(data)
def populate_interfaces(self, interfaces):
interfaces_dict = dict()
for if_data in interfaces:
if_dict = dict()
if_dict["MAC Address"] = if_data["Mac address"]
if_dict["Actual Speed"] = if_data["Actual speed"]
if_dict["MTU"] = if_data["MTU"]
if_dict["Admin State"] = if_data["Admin state"]
if_dict["Operational State"] = if_data["Operational state"]
if_name = if_dict["Interface Name"] = if_data["header"]
interfaces_dict[if_name] = if_dict
return interfaces_dict
FACT_SUBSETS = dict(
version=Version,
modules=Module,
interfaces=Interfaces
)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
def main():
""" main entry point for module execution
"""
OnyxFactsModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,482 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_interface
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage Interfaces on Mellanox ONYX network devices
description:
- This module provides declarative management of Interfaces
on Mellanox ONYX network devices.
notes:
options:
name:
description:
- Name of the Interface.
required: true
description:
description:
- Description of Interface.
enabled:
description:
- Interface link status.
type: bool
speed:
description:
- Interface link speed.
choices: ['1G', '10G', '25G', '40G', '50G', '56G', '100G']
mtu:
description:
- Maximum size of transmit packet.
aggregate:
description: List of Interfaces definitions.
duplex:
description:
- Interface link status
default: auto
choices: ['full', 'half', 'auto']
tx_rate:
description:
- Transmit rate in bits per second (bps).
rx_rate:
description:
- Receiver rate in bits per second (bps).
delay:
description:
- Time in seconds to wait before checking for the operational state on
remote device. This wait is applicable for operational state argument
which are I(state) with values C(up)/C(down).
default: 10
purge:
description:
- Purge Interfaces not defined in the aggregate parameter.
This applies only for logical interface.
default: false
type: bool
state:
description:
- State of the Interface configuration, C(up) means present and
operationally up and C(down) means present and operationally C(down)
default: present
choices: ['present', 'absent', 'up', 'down']
"""
EXAMPLES = """
- name: configure interface
onyx_interface:
name: Eth1/2
description: test-interface
speed: 100 GB
mtu: 512
- name: make interface up
onyx_interface:
name: Eth1/2
enabled: True
- name: make interface down
onyx_interface:
name: Eth1/2
enabled: False
- name: Check intent arguments
onyx_interface:
name: Eth1/2
state: up
- name: Config + intent
onyx_interface:
name: Eth1/2
enabled: False
state: down
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/2
- description test-interface
- mtu 512
- exit
"""
from copy import deepcopy
import re
from time import sleep
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import conditional
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
class OnyxInterfaceModule(BaseOnyxModule):
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
IF_LOOPBACK_REGEX = re.compile(r"^Loopback (\d+)$")
IF_TYPE_ETH = "ethernet"
IF_TYPE_LOOPBACK = "loopback"
IF_TYPE_VLAN = "vlan"
IF_TYPE_MAP = {
IF_TYPE_ETH: IF_ETH_REGEX,
IF_TYPE_VLAN: IF_VLAN_REGEX,
IF_TYPE_LOOPBACK: IF_LOOPBACK_REGEX,
}
UNSUPPORTED_ATTRS = {
IF_TYPE_ETH: (),
IF_TYPE_VLAN: ('speed', 'rx_rate', 'tx_rate'),
IF_TYPE_LOOPBACK: ('speed', 'mtu', 'rx_rate', 'tx_rate'),
}
UNSUPPORTED_STATES = {
IF_TYPE_ETH: ('absent',),
IF_TYPE_VLAN: (),
IF_TYPE_LOOPBACK: ('up', 'down'),
}
IF_MODIFIABLE_ATTRS = ('speed', 'description', 'mtu')
_interface_type = None
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(type='str'),
description=dict(),
speed=dict(choices=['1G', '10G', '25G', '40G', '50G', '56G', '100G']),
mtu=dict(type='int'),
enabled=dict(type='bool'),
delay=dict(default=10, type='int'),
state=dict(default='present',
choices=['present', 'absent', 'up', 'down']),
tx_rate=dict(),
rx_rate=dict(),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
purge=dict(default=False, type='bool'),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def validate_purge(self, value):
if value:
self._module.fail_json(
msg='Purge is not supported!')
def validate_duplex(self, value):
if value != 'auto':
self._module.fail_json(
msg='Duplex is not supported!')
def _get_interface_type(self, if_name):
if_type = None
if_id = None
for interface_type, interface_regex in iteritems(self.IF_TYPE_MAP):
match = interface_regex.match(if_name)
if match:
if_type = interface_type
if_id = match.group(1)
break
return if_type, if_id
def _set_if_type(self, params):
if_name = params['name']
if_type, if_id = self._get_interface_type(if_name)
if not if_id:
self._module.fail_json(
msg='unsupported interface: %s' % if_name)
params['if_type'] = if_type
params['if_id'] = if_id
def _check_supported_attrs(self, if_obj):
unsupported_attrs = self.UNSUPPORTED_ATTRS[self._interface_type]
for attr in unsupported_attrs:
val = if_obj[attr]
if val is not None:
self._module.fail_json(
msg='attribute %s is not supported for %s interface' % (
attr, self._interface_type))
req_state = if_obj['state']
unsupported_states = self.UNSUPPORTED_STATES[self._interface_type]
if req_state in unsupported_states:
self._module.fail_json(
msg='%s state is not supported for %s interface' % (
req_state, self._interface_type))
def _validate_interface_type(self):
for if_obj in self._required_config:
if_type = if_obj['if_type']
if not self._interface_type:
self._interface_type = if_type
elif self._interface_type != if_type:
self._module.fail_json(
msg='Cannot aggreagte interfaces from different types')
self._check_supported_attrs(if_obj)
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
self._set_if_type(req_item)
self._required_config.append(req_item)
else:
params = {
'name': module_params['name'],
'description': module_params['description'],
'speed': module_params['speed'],
'mtu': module_params['mtu'],
'state': module_params['state'],
'delay': module_params['delay'],
'enabled': module_params['enabled'],
'tx_rate': module_params['tx_rate'],
'rx_rate': module_params['rx_rate'],
}
self.validate_param_values(params)
self._set_if_type(params)
self._required_config.append(params)
self._validate_interface_type()
@classmethod
def get_if_name(cls, item):
return cls.get_config_attr(item, "header")
@classmethod
def get_admin_state(cls, item):
admin_state = cls.get_config_attr(item, "Admin state")
return str(admin_state).lower() == "enabled"
@classmethod
def get_oper_state(cls, item):
oper_state = cls.get_config_attr(item, "Operational state")
if not oper_state:
oper_state = cls.get_config_attr(item, "State")
return str(oper_state).lower()
@classmethod
def get_speed(cls, item):
speed = cls.get_config_attr(item, 'Actual speed')
if not speed:
return
try:
speed = int(speed.split()[0])
return "%dG" % speed
except ValueError:
return None
def _create_if_data(self, name, item):
regex = self.IF_TYPE_MAP[self._interface_type]
if_id = ''
match = regex.match(name)
if match:
if_id = match.group(1)
return dict(
name=name,
description=self.get_config_attr(item, 'Description'),
speed=self.get_speed(item),
mtu=self.get_mtu(item),
enabled=self.get_admin_state(item),
state=self.get_oper_state(item),
if_id=if_id)
def _get_interfaces_config(self):
return get_interfaces_config(self._module, self._interface_type)
def load_current_config(self):
self._current_config = dict()
config = self._get_interfaces_config()
if not config:
return
for item in config:
name = self.get_if_name(item)
self._current_config[name] = self._create_if_data(name, item)
def _generate_no_if_commands(self, req_if, curr_if):
if self._interface_type == self.IF_TYPE_ETH:
name = req_if['name']
self._module.fail_json(
msg='cannot remove ethernet interface %s' % name)
if not curr_if:
return
if_id = req_if['if_id']
if not if_id:
return
self._commands.append(
'no interface %s %s' % (self._interface_type, if_id))
def _add_commands_to_interface(self, req_if, cmd_list):
if not cmd_list:
return
if_id = req_if['if_id']
if not if_id:
return
self._commands.append(
'interface %s %s' % (self._interface_type, if_id))
self._commands.extend(cmd_list)
self._commands.append('exit')
def _generate_if_commands(self, req_if, curr_if):
enabled = req_if['enabled']
cmd_list = []
for attr_name in self.IF_MODIFIABLE_ATTRS:
candidate = req_if.get(attr_name)
running = curr_if.get(attr_name)
if candidate != running:
if candidate:
cmd = attr_name + ' ' + str(candidate)
if self._interface_type == self.IF_TYPE_ETH and \
attr_name in ('mtu', 'speed'):
cmd = cmd + ' ' + 'force'
cmd_list.append(cmd)
curr_enabled = curr_if.get('enabled', False)
if enabled is not None and enabled != curr_enabled:
cmd = 'shutdown'
if enabled:
cmd = "no %s" % cmd
cmd_list.append(cmd)
if cmd_list:
self._add_commands_to_interface(req_if, cmd_list)
def generate_commands(self):
for req_if in self._required_config:
name = req_if['name']
curr_if = self._current_config.get(name, {})
if not curr_if and self._interface_type == self.IF_TYPE_ETH:
self._module.fail_json(
msg='could not find ethernet interface %s' % name)
continue
req_state = req_if['state']
if req_state == 'absent':
self._generate_no_if_commands(req_if, curr_if)
else:
self._generate_if_commands(req_if, curr_if)
def _get_interfaces_rates(self):
return get_interfaces_config(self._module, self._interface_type,
"rates")
def _get_interfaces_status(self):
return get_interfaces_config(self._module, self._interface_type,
"status")
def _check_state(self, name, want_state, statuses):
curr_if = statuses.get(name, {})
if curr_if:
curr_if = curr_if[0]
curr_state = self.get_oper_state(curr_if).strip()
if curr_state is None or not conditional(want_state, curr_state):
return 'state eq(%s)' % want_state
def check_declarative_intent_params(self, result):
failed_conditions = []
delay_called = False
rates = None
statuses = None
for req_if in self._required_config:
want_state = req_if.get('state')
want_tx_rate = req_if.get('tx_rate')
want_rx_rate = req_if.get('rx_rate')
name = req_if['name']
if want_state not in ('up', 'down') and not want_tx_rate and not \
want_rx_rate:
continue
if not delay_called and result['changed']:
delay_called = True
delay = req_if['delay']
if delay > 0:
sleep(delay)
if want_state in ('up', 'down'):
if statuses is None:
statuses = self._get_interfaces_status() or {}
cond = self._check_state(name, want_state, statuses)
if cond:
failed_conditions.append(cond)
if_rates = None
if want_tx_rate or want_rx_rate:
if not rates:
rates = self._get_interfaces_rates()
if_rates = rates.get(name)
if if_rates:
if_rates = if_rates[0]
if want_tx_rate:
have_tx_rate = None
if if_rates:
have_tx_rate = if_rates.get('egress rate')
if have_tx_rate:
have_tx_rate = have_tx_rate.split()[0]
if have_tx_rate is None or not \
conditional(want_tx_rate, have_tx_rate.strip(),
cast=int):
failed_conditions.append('tx_rate ' + want_tx_rate)
if want_rx_rate:
have_rx_rate = None
if if_rates:
have_rx_rate = if_rates.get('ingress rate')
if have_rx_rate:
have_rx_rate = have_rx_rate.split()[0]
if have_rx_rate is None or not \
conditional(want_rx_rate, have_rx_rate.strip(),
cast=int):
failed_conditions.append('rx_rate ' + want_rx_rate)
return failed_conditions
def main():
""" main entry point for module execution
"""
OnyxInterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,284 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_l2_interface
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage Layer-2 interface on Mellanox ONYX network devices
description:
- This module provides declarative management of Layer-2 interface
on Mellanox ONYX network devices.
options:
name:
description:
- Name of the interface.
aggregate:
description:
- List of Layer-2 interface definitions.
mode:
description:
- Mode in which interface needs to be configured.
default: access
choices: ['access', 'trunk', 'hybrid']
access_vlan:
description:
- Configure given VLAN in access port.
trunk_allowed_vlans:
description:
- List of allowed VLANs in a given trunk port.
state:
description:
- State of the Layer-2 Interface configuration.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: configure Layer-2 interface
onyx_l2_interface:
name: Eth1/1
mode: access
access_vlan: 30
- name: remove Layer-2 interface configuration
onyx_l2_interface:
name: Eth1/1
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- interface ethernet 1/1
- switchport mode access
- switchport access vlan 30
"""
from copy import deepcopy
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
class OnyxL2InterfaceModule(BaseOnyxModule):
IFNAME_REGEX = re.compile(r"^.*(Eth\d+\/\d+|Mpo\d+|Po\d+)")
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(),
access_vlan=dict(type='int'),
trunk_allowed_vlans=dict(type='list', elements='int'),
state=dict(default='present',
choices=['present', 'absent']),
mode=dict(default='access',
choices=['access', 'hybrid', 'trunk']),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
self._required_config.append(req_item)
else:
params = {
'name': module_params['name'],
'access_vlan': module_params['access_vlan'],
'trunk_allowed_vlans': module_params['trunk_allowed_vlans'],
'mode': module_params['mode'],
'state': module_params['state'],
}
self.validate_param_values(params)
self._required_config.append(params)
def validate_access_vlan(self, value):
if value and not 1 <= int(value) <= 4094:
self._module.fail_json(msg='vlan id must be between 1 and 4094')
@classmethod
def get_allowed_vlans(cls, if_data):
allowed_vlans = cls.get_config_attr(if_data, 'Allowed vlans')
if allowed_vlans:
vlans = allowed_vlans.split(',')
allowed_vlans = [int(vlan.strip()) for vlan in vlans]
return allowed_vlans
@classmethod
def get_access_vlan(cls, if_data):
access_vlan = cls.get_config_attr(if_data, 'Access vlan')
if access_vlan:
return int(access_vlan)
def _create_switchport_data(self, if_name, if_data):
return {
'name': if_name,
'mode': self.get_config_attr(if_data, 'Mode'),
'access_vlan': self.get_access_vlan(if_data),
'trunk_allowed_vlans': self.get_allowed_vlans(if_data)
}
def _get_switchport_config(self):
return get_interfaces_config(self._module, 'switchport')
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
switchports_config = self._get_switchport_config()
if not switchports_config:
return
for if_name, if_data in iteritems(switchports_config):
self._current_config[if_name] = \
self._create_switchport_data(if_name, if_data)
def _get_switchport_command_name(self, if_name):
if if_name.startswith('Eth'):
return if_name.replace("Eth", "ethernet ")
if if_name.startswith('Po'):
return if_name.replace("Po", "port-channel ")
if if_name.startswith('Mpo'):
return if_name.replace("Mpo", "mlag-port-channel ")
self._module.fail_json(
msg='invalid interface name: %s' % if_name)
def _add_interface_commands(self, if_name, commands):
if_cmd_name = self._get_switchport_command_name(if_name)
self._commands.append("interface %s" % if_cmd_name)
self._commands.extend(commands)
self._commands.append('exit')
def _generate_no_switchport_commands(self, if_name):
commands = ['no switchport force']
self._add_interface_commands(if_name, commands)
def _generate_switchport_commands(self, if_name, req_conf):
commands = []
curr_conf = self._current_config.get(if_name, {})
curr_mode = curr_conf.get('mode')
req_mode = req_conf.get('mode')
if req_mode != curr_mode:
commands.append('switchport mode %s' % req_mode)
curr_access_vlan = curr_conf.get('access_vlan')
req_access_vlan = req_conf.get('access_vlan')
if curr_access_vlan != req_access_vlan and req_access_vlan:
commands.append('switchport access vlan %s' % req_access_vlan)
curr_trunk_vlans = curr_conf.get('trunk_allowed_vlans') or set()
if curr_trunk_vlans:
curr_trunk_vlans = set(curr_trunk_vlans)
req_trunk_vlans = req_conf.get('trunk_allowed_vlans') or set()
if req_trunk_vlans:
req_trunk_vlans = set(req_trunk_vlans)
if req_mode != 'access' and curr_trunk_vlans != req_trunk_vlans:
removed_vlans = curr_trunk_vlans - req_trunk_vlans
for vlan_id in removed_vlans:
commands.append('switchport %s allowed-vlan remove %s' %
(req_mode, vlan_id))
added_vlans = req_trunk_vlans - curr_trunk_vlans
for vlan_id in added_vlans:
commands.append('switchport %s allowed-vlan add %s' %
(req_mode, vlan_id))
if commands:
self._add_interface_commands(if_name, commands)
def generate_commands(self):
for req_conf in self._required_config:
state = req_conf['state']
if_name = req_conf['name']
if state == 'absent':
if if_name in self._current_config:
self._generate_no_switchport_commands(if_name)
else:
self._generate_switchport_commands(if_name, req_conf)
def _generate_vlan_commands(self, vlan_id, req_conf):
curr_vlan = self._current_config.get(vlan_id, {})
if not curr_vlan:
cmd = "vlan " + vlan_id
self._commands.append("vlan %s" % vlan_id)
self._commands.append("exit")
vlan_name = req_conf['vlan_name']
if vlan_name:
if vlan_name != curr_vlan.get('vlan_name'):
self._commands.append("vlan %s name %s" % (vlan_id, vlan_name))
curr_members = set(curr_vlan.get('interfaces', []))
req_members = req_conf['interfaces']
mode = req_conf['mode']
for member in req_members:
if member in curr_members:
continue
if_name = self.get_switchport_command_name(member)
cmd = "interface %s switchport mode %s" % (if_name, mode)
self._commands.append(cmd)
cmd = "interface %s switchport %s allowed-vlan add %s" % (
if_name, mode, vlan_id)
self._commands.append(cmd)
req_members = set(req_members)
for member in curr_members:
if member in req_members:
continue
if_name = self.get_switchport_command_name(member)
cmd = "interface %s switchport %s allowed-vlan remove %s" % (
if_name, mode, vlan_id)
self._commands.append(cmd)
def main():
""" main entry point for module execution
"""
OnyxL2InterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,292 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_l3_interface
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage L3 interfaces on Mellanox ONYX network devices
description:
- This module provides declarative management of L3 interfaces
on Mellanox ONYX network devices.
options:
name:
description:
- Name of the L3 interface.
ipv4:
description:
- IPv4 of the L3 interface.
ipv6:
description:
- IPv6 of the L3 interface (not supported for now).
aggregate:
description: List of L3 interfaces definitions
purge:
description:
- Purge L3 interfaces not defined in the I(aggregate) parameter.
default: false
type: bool
state:
description:
- State of the L3 interface configuration.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: Set Eth1/1 IPv4 address
onyx_l3_interface:
name: Eth1/1
ipv4: 192.168.0.1/24
- name: Remove Eth1/1 IPv4 address
onyx_l3_interface:
name: Eth1/1
state: absent
- name: Set IP addresses on aggregate
onyx_l3_interface:
aggregate:
- { name: Eth1/1, ipv4: 192.168.2.10/24 }
- { name: Eth1/2, ipv4: 192.168.3.10/24 }
- name: Remove IP addresses on aggregate
onyx_l3_interface:
aggregate:
- { name: Eth1/1, ipv4: 192.168.2.10/24 }
- { name: Eth1/2, ipv4: 192.168.3.10/24 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- interfaces ethernet 1/1 ip address 192.168.0.1 /24
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
class OnyxL3InterfaceModule(BaseOnyxModule):
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
IF_LOOPBACK_REGEX = re.compile(r"^Loopback (\d+)$")
IF_TYPE_ETH = "ethernet"
IF_TYPE_LOOPBACK = "loopback"
IF_TYPE_VLAN = "vlan"
IF_TYPE_MAP = {
IF_TYPE_ETH: IF_ETH_REGEX,
IF_TYPE_VLAN: IF_VLAN_REGEX,
IF_TYPE_LOOPBACK: IF_LOOPBACK_REGEX,
}
IP_ADDR_ATTR_MAP = {
IF_TYPE_ETH: 'IP Address',
IF_TYPE_VLAN: 'Internet Address',
IF_TYPE_LOOPBACK: 'Internet Address',
}
_purge = False
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(type='str'),
ipv4=dict(type='str'),
ipv6=dict(type='str'),
state=dict(default='present',
choices=['present', 'absent', 'enabled', 'disabled']),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
purge=dict(default=False, type='bool'),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def _get_interface_type(self, if_name):
if_type = None
if_id = None
for interface_type, interface_regex in iteritems(self.IF_TYPE_MAP):
match = interface_regex.match(if_name)
if match:
if_type = interface_type
if_id = match.group(1)
break
return if_type, if_id
def _set_if_type(self, params):
if_name = params['name']
if_type, if_id = self._get_interface_type(if_name)
if not if_id:
self._module.fail_json(
msg='unsupported interface: %s' % if_name)
params['if_type'] = if_type
params['if_id'] = if_id
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
self._purge = module_params.get('purge', False)
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
self._set_if_type(req_item)
self._required_config.append(req_item)
else:
params = {
'name': module_params['name'],
'ipv4': module_params['ipv4'],
'ipv6': module_params['ipv6'],
'state': module_params['state'],
}
self.validate_param_values(params)
self._set_if_type(params)
self._required_config.append(params)
def _get_interfaces_config(self, interface_type):
return get_interfaces_config(self._module, interface_type)
def _parse_interfaces_config(self, if_type, if_config):
ipaddr_attr = self.IP_ADDR_ATTR_MAP[if_type]
for if_data in if_config:
if_name = self.get_config_attr(if_data, 'header')
regex = self.IF_TYPE_MAP[if_type]
match = regex.match(if_name)
if not match:
continue
ipv4 = self.get_config_attr(if_data, ipaddr_attr)
if ipv4:
ipv4 = ipv4.replace(' ', '')
ipv6 = self.get_config_attr(if_data, 'IPv6 address(es)')
if ipv6:
ipv6 = ipv6.replace('[primary]', '')
ipv6 = ipv6.strip()
if_id = match.group(1)
switchport = self.get_config_attr(if_data, 'Switchport mode')
if_obj = {
'name': if_name,
'if_id': if_id,
'if_type': if_type,
'ipv4': ipv4,
'ipv6': ipv6,
'switchport': switchport,
}
self._current_config[if_name] = if_obj
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
if_types = set([if_obj['if_type'] for if_obj in self._required_config])
for if_type in if_types:
if_config = self._get_interfaces_config(if_type)
if not if_config:
continue
self._parse_interfaces_config(if_type, if_config)
def _generate_no_ip_commands(self, req_conf, curr_conf):
curr_ip = curr_conf.get('ipv4')
if_type = req_conf['if_type']
if_id = req_conf['if_id']
if curr_ip:
cmd = "interface %s %s no ip address" % (if_type, if_id)
self._commands.append(cmd)
curr_ipv6 = curr_conf.get('ipv6')
if curr_ipv6:
cmd = "interface %s %s no ipv6 address %s" % (
if_type, if_id, curr_ipv6)
self._commands.append(cmd)
def _generate_ip_commands(self, req_conf, curr_conf):
curr_ipv4 = curr_conf.get('ipv4')
req_ipv4 = req_conf.get('ipv4')
curr_ipv6 = curr_conf.get('ipv6')
req_ipv6 = req_conf.get('ipv6')
if_type = req_conf['if_type']
if_id = req_conf['if_id']
switchport = curr_conf.get('switchport')
if switchport:
cmd = "interface %s %s no switchport force" % (if_type, if_id)
self._commands.append(cmd)
if curr_ipv4 != req_ipv4:
cmd = "interface %s %s ip address %s" % (if_type, if_id, req_ipv4)
self._commands.append(cmd)
if curr_ipv6 != req_ipv6:
cmd = "interface %s %s ipv6 address %s" % (
if_type, if_id, req_ipv6)
self._commands.append(cmd)
def generate_commands(self):
req_interfaces = set()
for req_conf in self._required_config:
state = req_conf['state']
if_name = req_conf['name']
curr_conf = self._current_config.get(if_name, {})
if state == 'absent':
self._generate_no_ip_commands(req_conf, curr_conf)
else:
req_interfaces.add(if_name)
self._generate_ip_commands(req_conf, curr_conf)
if self._purge:
for if_name, curr_conf in iteritems(self._current_config):
if if_name not in req_interfaces:
self._generate_no_ip_commands(req_conf, curr_conf)
def main():
""" main entry point for module execution
"""
OnyxL3InterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,341 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_linkagg
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage link aggregation groups on Mellanox ONYX network devices
description:
- This module provides declarative management of link aggregation groups
on Mellanox ONYX network devices.
options:
name:
description:
- Name of the link aggregation group.
required: true
mode:
description:
- Mode of the link aggregation group. A value of C(on) will enable LACP.
C(active) configures the link to actively information about the state of the link,
or it can be configured in C(passive) mode ie. send link state information only when
received them from another link.
default: on
choices: ['on', 'active', 'passive']
members:
description:
- List of members interfaces of the link aggregation group. The value can be
single interface or list of interfaces.
required: true
aggregate:
description: List of link aggregation definitions.
purge:
description:
- Purge link aggregation groups not defined in the I(aggregate) parameter.
default: false
type: bool
state:
description:
- State of the link aggregation group.
default: present
choices: ['present', 'absent', 'up', 'down']
"""
EXAMPLES = """
- name: configure link aggregation group
onyx_linkagg:
name: Po1
members:
- Eth1/1
- Eth1/2
- name: remove configuration
onyx_linkagg:
name: Po1
state: absent
- name: Create aggregate of linkagg definitions
onyx_linkagg:
aggregate:
- { name: Po1, members: [Eth1/1] }
- { name: Po2, members: [Eth1/2] }
- name: Remove aggregate of linkagg definitions
onyx_linkagg:
aggregate:
- name: Po1
- name: Po2
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- interface port-channel 1
- exit
- interface ethernet 1/1 channel-group 1 mode on
- interface ethernet 1/2 channel-group 1 mode on
"""
import re
from copy import deepcopy
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import get_interfaces_config
class OnyxLinkAggModule(BaseOnyxModule):
LAG_ID_REGEX = re.compile(r"^\d+ (Po\d+|Mpo\d+)\(([A-Z])\)$")
LAG_NAME_REGEX = re.compile(r"^(Po|Mpo)(\d+)$")
IF_NAME_REGEX = re.compile(r"^(Eth\d+\/\d+|Eth\d+\/\d+\/\d+)(.*)$")
PORT_CHANNEL = 'port-channel'
CHANNEL_GROUP = 'channel-group'
MLAG_PORT_CHANNEL = 'mlag-port-channel'
MLAG_CHANNEL_GROUP = 'mlag-channel-group'
LAG_TYPE = 'lag'
MLAG_TYPE = 'mlag'
IF_TYPE_MAP = dict(
lag=PORT_CHANNEL,
mlag=MLAG_PORT_CHANNEL
)
_purge = False
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(type='str'),
members=dict(type='list'),
mode=dict(default='on', choices=['active', 'on', 'passive']),
state=dict(default='present', choices=['present', 'absent']),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
purge=dict(default=False, type='bool'),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def _get_lag_type(self, lag_name):
match = self.LAG_NAME_REGEX.match(lag_name)
if match:
prefix = match.group(1)
if prefix == "Po":
return self.LAG_TYPE
return self.MLAG_TYPE
self._module.fail_json(
msg='invalid lag name: %s, lag name should start with Po or '
'Mpo' % lag_name)
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
self._purge = module_params.get('purge', False)
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
req_item['type'] = self._get_lag_type(req_item['name'])
self._required_config.append(req_item)
else:
params = {
'name': module_params['name'],
'state': module_params['state'],
'members': module_params['members'],
'mode': module_params['mode'],
'type': self._get_lag_type(module_params['name']),
}
self.validate_param_values(params)
self._required_config.append(params)
@classmethod
def _extract_lag_name(cls, header):
match = cls.LAG_ID_REGEX.match(header)
state = None
lag_name = None
if match:
state = 'up' if match.group(2) == 'U' else 'down'
lag_name = match.group(1)
return lag_name, state
@classmethod
def _extract_if_name(cls, member):
match = cls.IF_NAME_REGEX.match(member)
if match:
return match.group(1)
@classmethod
def _extract_lag_members(cls, lag_type, lag_item):
members = ""
if lag_type == cls.LAG_TYPE:
members = cls.get_config_attr(lag_item, "Member Ports")
else:
for attr_name, attr_val in iteritems(lag_item):
if attr_name.startswith('Local Ports'):
members = attr_val
return [cls._extract_if_name(member) for member in members.split()]
def _get_port_channels(self, if_type):
return get_interfaces_config(self._module, if_type, flags="summary")
def _parse_port_channels_summary(self, lag_type, lag_summary):
if lag_type == self.MLAG_TYPE:
lag_summary = lag_summary.get('MLAG Port-Channel Summary', {})
for lag_key, lag_data in iteritems(lag_summary):
lag_name, state = self._extract_lag_name(lag_key)
if not lag_name:
continue
lag_members = self._extract_lag_members(lag_type, lag_data[0])
lag_obj = dict(
name=lag_name,
state=state,
members=lag_members
)
self._current_config[lag_name] = lag_obj
def load_current_config(self):
self._current_config = dict()
lag_types = set([lag_obj['type'] for lag_obj in self._required_config])
for lag_type in lag_types:
if_type = self.IF_TYPE_MAP[lag_type]
lag_summary = self._get_port_channels(if_type)
if lag_summary:
self._parse_port_channels_summary(lag_type, lag_summary)
with open('/tmp/linagg.txt', 'w') as fp:
fp.write('current_config: %s\n' % self._current_config)
fp.write('required_config: %s\n' % self._required_config)
def _get_interface_command_suffix(self, if_name):
if if_name.startswith('Eth'):
return if_name.replace("Eth", "ethernet ")
if if_name.startswith('Po'):
return if_name.replace("Po", "port-channel ")
if if_name.startswith('Mpo'):
return if_name.replace("Mpo", "mlag-port-channel ")
self._module.fail_json(
msg='invalid interface name: %s' % if_name)
def _get_channel_group(self, if_name):
if if_name.startswith('Po'):
return if_name.replace("Po", "channel-group ")
if if_name.startswith('Mpo'):
return if_name.replace("Mpo", "mlag-channel-group ")
self._module.fail_json(
msg='invalid interface name: %s' % if_name)
def _generate_no_linkagg_commands(self, lag_name):
suffix = self._get_interface_command_suffix(lag_name)
command = 'no interface %s' % suffix
self._commands.append(command)
def _generate_linkagg_commands(self, lag_name, req_lag):
curr_lag = self._current_config.get(lag_name, {})
if not curr_lag:
suffix = self._get_interface_command_suffix(lag_name)
self._commands.append("interface %s" % suffix)
self._commands.append("exit")
curr_members = set(curr_lag.get('members', []))
req_members = set(req_lag.get('members') or [])
lag_mode = req_lag['mode']
if req_members != curr_members:
channel_group = self._get_channel_group(lag_name)
channel_group_type = channel_group.split()[0]
for member in req_members:
if member in curr_members:
continue
suffix = self._get_interface_command_suffix(member)
self._commands.append(
"interface %s %s mode %s" %
(suffix, channel_group, lag_mode))
for member in curr_members:
if member in req_members:
continue
suffix = self._get_interface_command_suffix(member)
self._commands.append(
"interface %s no %s" % (suffix, channel_group_type))
req_state = req_lag.get('state')
if req_state in ('up', 'down'):
curr_state = curr_lag.get('state')
if curr_state != req_state:
suffix = self._get_interface_command_suffix(lag_name)
cmd = "interface %s " % suffix
if req_state == 'up':
cmd += 'no shutdown'
else:
cmd += 'shutdown'
self._commands.append(cmd)
def generate_commands(self):
req_lags = set()
for req_conf in self._required_config:
state = req_conf['state']
lag_name = req_conf['name']
if state == 'absent':
if lag_name in self._current_config:
self._generate_no_linkagg_commands(lag_name)
else:
req_lags.add(lag_name)
self._generate_linkagg_commands(lag_name, req_conf)
if self._purge:
for lag_name in self._current_config:
if lag_name not in req_lags:
self._generate_no_linkagg_commands(lag_name)
def check_declarative_intent_params(self, result):
pass
def main():
""" main entry point for module execution
"""
OnyxLinkAggModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,117 @@
#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_lldp
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage LLDP configuration on Mellanox ONYX network devices
description:
- This module provides declarative management of LLDP service configuration
on Mellanox ONYX network devices.
options:
state:
description:
- State of the LLDP protocol configuration.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: Enable LLDP protocol
onyx_lldp:
state: present
- name: Disable LLDP protocol
onyx_lldp:
state: lldp
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- lldp
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxLldpModule(BaseOnyxModule):
LLDP_ENTRY = 'LLDP'
SHOW_LLDP_CMD = 'show lldp local'
@classmethod
def _get_element_spec(cls):
return dict(
state=dict(default='present', choices=['present', 'absent']),
)
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def get_required_config(self):
self._required_config = dict()
module_params = self._module.params
params = {
'state': module_params['state'],
}
self.validate_param_values(params)
self._required_config.update(params)
def _get_lldp_config(self):
return show_cmd(self._module, self.SHOW_LLDP_CMD)
def load_current_config(self):
self._current_config = dict()
state = 'absent'
config = self._get_lldp_config() or dict()
for item in config:
lldp_state = item.get(self.LLDP_ENTRY)
if lldp_state is not None:
if lldp_state == 'enabled':
state = 'present'
break
self._current_config['state'] = state
def generate_commands(self):
req_state = self._required_config['state']
curr_state = self._current_config['state']
if curr_state != req_state:
cmd = 'lldp'
if req_state == 'absent':
cmd = 'no %s' % cmd
self._commands.append(cmd)
def main():
""" main entry point for module execution
"""
OnyxLldpModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,229 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_lldp_interface
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage LLDP interfaces configuration on Mellanox ONYX network devices
description:
- This module provides declarative management of LLDP interfaces
configuration on Mellanox ONYX network devices.
options:
name:
description:
- Name of the interface LLDP should be configured on.
aggregate:
description: List of interfaces LLDP should be configured on.
purge:
description:
- Purge interfaces not defined in the aggregate parameter.
type: bool
default: false
state:
description:
- State of the LLDP configuration.
default: present
choices: ['present', 'absent', 'enabled', 'disabled']
"""
EXAMPLES = """
- name: Configure LLDP on specific interfaces
onyx_lldp_interface:
name: Eth1/1
state: present
- name: Disable LLDP on specific interfaces
onyx_lldp_interface:
name: Eth1/1
state: disabled
- name: Enable LLDP on specific interfaces
onyx_lldp_interface:
name: Eth1/1
state: enabled
- name: Delete LLDP on specific interfaces
onyx_lldp_interface:
name: Eth1/1
state: absent
- name: Create aggregate of LLDP interface configurations
onyx_lldp_interface:
aggregate:
- { name: Eth1/1 }
- { name: Eth1/2 }
state: present
- name: Delete aggregate of LLDP interface configurations
onyx_lldp_interface:
aggregate:
- { name: Eth1/1 }
- { name: Eth1/2 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- interface ethernet 1/1 lldp transmit
- interface ethernet 1/1 lldp receive
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxLldpInterfaceModule(BaseOnyxModule):
IF_NAME_REGEX = re.compile(r"^(Eth\d+\/\d+|Eth\d+\/\d+\d+)$")
_purge = False
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(type='str'),
state=dict(default='present',
choices=['present', 'absent', 'enabled', 'disabled']),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
purge=dict(default=False, type='bool'),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
self._purge = module_params.get('purge', False)
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
self._required_config.append(req_item)
else:
params = {
'name': module_params['name'],
'state': module_params['state'],
}
self.validate_param_values(params)
self._required_config.append(params)
def _create_if_lldp_data(self, if_name, if_lldp_data):
return {
'name': if_name,
'receive': self.get_config_attr(if_lldp_data, 'Receive'),
'transmit': self.get_config_attr(if_lldp_data, 'Transmit'),
}
def _get_lldp_config(self):
return show_cmd(self._module, "show lldp interfaces")
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
lldp_config = self._get_lldp_config()
if not lldp_config:
return
for if_name, if_lldp_data in iteritems(lldp_config):
match = self.IF_NAME_REGEX.match(if_name)
if not match:
continue
if if_lldp_data:
if_lldp_data = if_lldp_data[0]
self._current_config[if_name] = \
self._create_if_lldp_data(if_name, if_lldp_data)
def _get_interface_cmd_name(self, if_name):
return if_name.replace("Eth", "ethernet ")
def _add_if_lldp_commands(self, if_name, flag, enable):
cmd_prefix = "interface %s " % self._get_interface_cmd_name(if_name)
lldp_cmd = "lldp %s" % flag
if not enable:
lldp_cmd = 'no %s' % lldp_cmd
self._commands.append(cmd_prefix + lldp_cmd)
def _gen_lldp_commands(self, if_name, req_state, curr_conf):
curr_receive = curr_conf.get('receive')
curr_transmit = curr_conf.get('transmit')
enable = (req_state == 'Enabled')
if curr_receive != req_state:
flag = 'receive'
self._add_if_lldp_commands(if_name, flag, enable)
if curr_transmit != req_state:
flag = 'transmit'
self._add_if_lldp_commands(if_name, flag, enable)
def generate_commands(self):
req_interfaces = set()
for req_conf in self._required_config:
state = req_conf['state']
if_name = req_conf['name']
if state in ('absent', 'disabled'):
req_state = 'Disabled'
else:
req_interfaces.add(if_name)
req_state = 'Enabled'
curr_conf = self._current_config.get(if_name, {})
self._gen_lldp_commands(if_name, req_state, curr_conf)
if self._purge:
for if_name, curr_conf in iteritems(self._current_config):
if if_name not in req_interfaces:
req_state = 'Disabled'
self._gen_lldp_commands(if_name, req_state, curr_conf)
def main():
""" main entry point for module execution
"""
OnyxLldpInterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,227 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_magp
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage MAGP protocol on Mellanox ONYX network devices
description:
- This module provides declarative management of MAGP protocol on vlan
interface of Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
magp_id:
description:
- "MAGP instance number 1-255"
required: true
interface:
description:
- VLAN Interface name.
required: true
state:
description:
- MAGP state.
default: present
choices: ['present', 'absent', 'enabled', 'disabled']
router_ip:
description:
- MAGP router IP address.
router_mac:
description:
- MAGP router MAC address.
"""
EXAMPLES = """
- name: run add vlan interface with magp
onyx_magp:
magp_id: 103
router_ip: 192.168.8.2
router_mac: AA:1B:2C:3D:4E:5F
interface: Vlan 1002
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface vlan 234 magp 103
- exit
- interface vlan 234 magp 103 ip virtual-router address 1.2.3.4
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxMagpModule(BaseOnyxModule):
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
@classmethod
def _get_element_spec(cls):
return dict(
magp_id=dict(type='int', required=True),
state=dict(default='present',
choices=['present', 'absent', 'enabled', 'disabled']),
interface=dict(required=True),
router_ip=dict(),
router_mac=dict(),
)
def init_module(self):
""" Ansible module initialization
"""
element_spec = self._get_element_spec()
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def validate_magp_id(self, value):
if value and not 1 <= int(value) <= 255:
self._module.fail_json(msg='magp id must be between 1 and 255')
def get_required_config(self):
module_params = self._module.params
interface = module_params['interface']
match = self.IF_VLAN_REGEX.match(interface)
vlan_id = 0
if match:
vlan_id = int(match.group(1))
else:
self._module.fail_json(
msg='Invalid interface name: should be "Vlan <vlan_id>"')
self._required_config = dict(
magp_id=module_params['magp_id'],
state=module_params['state'],
vlan_id=vlan_id,
router_ip=module_params['router_ip'],
router_mac=module_params['router_mac'])
self.validate_param_values(self._required_config)
@classmethod
def get_magp_id(cls, item):
header = cls.get_config_attr(item, "header")
return int(header.split()[1])
def _create_magp_instance_data(self, magp_id, item):
vlan_id = int(self.get_config_attr(item, "Interface vlan"))
state = self.get_config_attr(item, "Admin state").lower()
return dict(
magp_id=magp_id,
state=state,
vlan_id=vlan_id,
router_ip=self.get_config_attr(item, "Virtual IP"),
router_mac=self.get_config_attr(item, "Virtual MAC"))
def _update_magp_data(self, magp_data):
for magp_item in magp_data:
magp_id = self.get_magp_id(magp_item)
inst_data = self._create_magp_instance_data(magp_id, magp_item)
self._current_config[magp_id] = inst_data
def _get_magp_config(self):
cmd = "show magp"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
magp_data = self._get_magp_config()
if magp_data:
self._update_magp_data(magp_data)
def _generate_no_magp_commands(self):
req_vlan_id = self._required_config['vlan_id']
req_magp_id = self._required_config['magp_id']
curr_magp_data = self._current_config.get(req_magp_id)
if not curr_magp_data:
return
curr_vlan_id = curr_magp_data.get(req_vlan_id)
if curr_vlan_id == req_vlan_id:
cmd = 'interface vlan %s no magp %s' % (req_vlan_id, req_magp_id)
self._commands.append(cmd)
def _generate_magp_commands(self, req_state):
req_vlan_id = self._required_config['vlan_id']
req_magp_id = self._required_config['magp_id']
curr_magp_data = self._current_config.get(req_magp_id, dict())
curr_vlan_id = curr_magp_data.get('vlan_id')
magp_prefix = 'interface vlan %s magp %s' % (req_vlan_id, req_magp_id)
create_new_magp = False
if curr_vlan_id != req_vlan_id:
if curr_vlan_id:
cmd = 'interface vlan %s no magp %s' % (
curr_vlan_id, req_magp_id)
self._commands.append(cmd)
create_new_magp = True
self._commands.append(magp_prefix)
self._commands.append('exit')
req_router_ip = self._required_config['router_ip']
curr_router_ip = curr_magp_data.get('router_ip')
if req_router_ip:
if curr_router_ip != req_router_ip or create_new_magp:
cmd = '%s ip virtual-router address %s' % (
magp_prefix, req_router_ip)
self._commands.append(cmd)
else:
if curr_router_ip and curr_router_ip != '0.0.0.0':
cmd = '%s no ip virtual-router address' % magp_prefix
self._commands.append(cmd)
req_router_mac = self._required_config['router_mac']
curr_router_mac = curr_magp_data.get('router_mac')
if curr_router_mac:
curr_router_mac = curr_router_mac.lower()
if req_router_mac:
req_router_mac = req_router_mac.lower()
if curr_router_mac != req_router_mac or create_new_magp:
cmd = '%s ip virtual-router mac-address %s' % (
magp_prefix, req_router_mac)
self._commands.append(cmd)
else:
if curr_router_mac and curr_router_mac != '00:00:00:00:00:00':
cmd = '%s no ip virtual-router mac-address' % magp_prefix
self._commands.append(cmd)
if req_state in ('enabled', 'disabled'):
curr_state = curr_magp_data.get('state', 'enabled')
if curr_state != req_state:
if req_state == 'enabled':
suffix = 'no shutdown'
else:
suffix = 'shutdown'
cmd = '%s %s' % (magp_prefix, suffix)
self._commands.append(cmd)
def generate_commands(self):
req_state = self._required_config['state']
if req_state == 'absent':
return self._generate_no_magp_commands()
return self._generate_magp_commands(req_state)
def main():
""" main entry point for module execution
"""
OnyxMagpModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,210 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_mlag_ipl
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage IPL (inter-peer link) on Mellanox ONYX network devices
description:
- This module provides declarative management of IPL (inter-peer link)
management on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
name:
description:
- Name of the interface (port-channel) IPL should be configured on.
required: true
vlan_interface:
description:
- Name of the IPL vlan interface.
state:
description:
- IPL state.
default: present
choices: ['present', 'absent']
peer_address:
description:
- IPL peer IP address.
"""
EXAMPLES = """
- name: run configure ipl
onyx_mlag_ipl:
name: Po1
vlan_interface: Vlan 322
state: present
peer_address: 192.168.7.1
- name: run remove ipl
onyx_mlag_ipl:
name: Po1
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface port-channel 1 ipl 1
- interface vlan 1024 ipl 1 peer-address 10.10.10.10
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxMlagIplModule(BaseOnyxModule):
VLAN_IF_REGEX = re.compile(r'^Vlan \d+')
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(required=True),
state=dict(default='present',
choices=['present', 'absent']),
peer_address=dict(),
vlan_interface=dict(),
)
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(
name=module_params['name'],
state=module_params['state'],
peer_address=module_params['peer_address'],
vlan_interface=module_params['vlan_interface'])
self.validate_param_values(self._required_config)
def _update_mlag_data(self, mlag_data):
if not mlag_data:
return
mlag_summary = mlag_data.get("MLAG IPLs Summary", {})
ipl_id = "1"
ipl_list = mlag_summary.get(ipl_id)
if ipl_list:
ipl_data = ipl_list[0]
vlan_id = ipl_data.get("Vlan Interface")
vlan_interface = ""
if vlan_id != "N/A":
vlan_interface = "Vlan %s" % vlan_id
peer_address = ipl_data.get("Peer IP address")
name = ipl_data.get("Group Port-Channel")
self._current_config = dict(
name=name,
peer_address=peer_address,
vlan_interface=vlan_interface)
def _show_mlag_data(self):
cmd = "show mlag"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
mlag_data = self._show_mlag_data()
self._update_mlag_data(mlag_data)
def _get_interface_cmd_name(self, if_name):
if if_name.startswith('Po'):
return if_name.replace("Po", "port-channel ")
self._module.fail_json(
msg='invalid interface name: %s' % if_name)
def _generate_port_channel_command(self, if_name, enable):
if_cmd_name = self._get_interface_cmd_name(if_name)
if enable:
ipl_cmd = 'ipl 1'
else:
ipl_cmd = "no ipl 1"
cmd = "interface %s %s" % (if_cmd_name, ipl_cmd)
return cmd
def _generate_vlan_if_command(self, if_name, enable, peer_address):
if_cmd_name = if_name.lower()
if enable:
ipl_cmd = 'ipl 1 peer-address %s' % peer_address
else:
ipl_cmd = "no ipl 1"
cmd = "interface %s %s" % (if_cmd_name, ipl_cmd)
return cmd
def _generate_no_ipl_commands(self):
curr_interface = self._current_config.get('name')
req_interface = self._required_config.get('name')
if curr_interface == req_interface:
cmd = self._generate_port_channel_command(
req_interface, enable=False)
self._commands.append(cmd)
def _generate_ipl_commands(self):
curr_interface = self._current_config.get('name')
req_interface = self._required_config.get('name')
if curr_interface != req_interface:
if curr_interface and curr_interface != 'N/A':
cmd = self._generate_port_channel_command(
curr_interface, enable=False)
self._commands.append(cmd)
cmd = self._generate_port_channel_command(
req_interface, enable=True)
self._commands.append(cmd)
curr_vlan = self._current_config.get('vlan_interface')
req_vlan = self._required_config.get('vlan_interface')
add_peer = False
if curr_vlan != req_vlan:
add_peer = True
if curr_vlan:
cmd = self._generate_vlan_if_command(curr_vlan, enable=False,
peer_address=None)
self._commands.append(cmd)
curr_peer = self._current_config.get('peer_address')
req_peer = self._required_config.get('peer_address')
if req_peer != curr_peer:
add_peer = True
if add_peer and req_peer:
cmd = self._generate_vlan_if_command(req_vlan, enable=True,
peer_address=req_peer)
self._commands.append(cmd)
def generate_commands(self):
state = self._required_config['state']
if state == 'absent':
self._generate_no_ipl_commands()
else:
self._generate_ipl_commands()
def main():
""" main entry point for module execution
"""
OnyxMlagIplModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,179 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_mlag_vip
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Configures MLAG VIP on Mellanox ONYX network devices
description:
- This module provides declarative management of MLAG virtual IPs
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
ipaddress:
description:
- Virtual IP address of the MLAG. Required if I(state=present).
group_name:
description:
- MLAG group name. Required if I(state=present).
mac_address:
description:
- MLAG system MAC address. Required if I(state=present).
state:
description:
- MLAG VIP state.
choices: ['present', 'absent']
delay:
description:
- Delay interval, in seconds, waiting for the changes on mlag VIP to take
effect.
default: 12
"""
EXAMPLES = """
- name: configure mlag-vip
onyx_mlag_vip:
ipaddress: 50.3.3.1/24
group_name: ansible-test-group
mac_address: 00:11:12:23:34:45
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- mlag-vip ansible_test_group ip 50.3.3.1 /24 force
- no mlag shutdown
"""
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxMLagVipModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
element_spec = dict(
ipaddress=dict(),
group_name=dict(),
mac_address=dict(),
delay=dict(type='int', default=12),
state=dict(choices=['present', 'absent'], default='present'),
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def get_required_config(self):
module_params = self._module.params
lag_params = {
'ipaddress': module_params['ipaddress'],
'group_name': module_params['group_name'],
'mac_address': module_params['mac_address'],
'delay': module_params['delay'],
'state': module_params['state'],
}
self.validate_param_values(lag_params)
self._required_config = lag_params
def _show_mlag_cmd(self, cmd):
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _show_mlag(self):
cmd = "show mlag"
return self._show_mlag_cmd(cmd)
def _show_mlag_vip(self):
cmd = "show mlag-vip"
return self._show_mlag_cmd(cmd)
def load_current_config(self):
self._current_config = dict()
mlag_config = self._show_mlag()
mlag_vip_config = self._show_mlag_vip()
if mlag_vip_config:
mlag_vip = mlag_vip_config.get("MLAG-VIP", {})
self._current_config['group_name'] = \
mlag_vip.get("MLAG group name")
self._current_config['ipaddress'] = \
mlag_vip.get("MLAG VIP address")
if mlag_config:
self._current_config['mac_address'] = \
mlag_config.get("System-mac")
def generate_commands(self):
state = self._required_config['state']
if state == 'present':
self._generate_mlag_vip_cmds()
else:
self._generate_no_mlag_vip_cmds()
def _generate_mlag_vip_cmds(self):
current_group = self._current_config.get('group_name')
current_ip = self._current_config.get('ipaddress')
current_mac = self._current_config.get('mac_address')
if current_mac:
current_mac = current_mac.lower()
req_group = self._required_config.get('group_name')
req_ip = self._required_config.get('ipaddress')
req_mac = self._required_config.get('mac_address')
if req_mac:
req_mac = req_mac.lower()
if req_group != current_group or req_ip != current_ip:
ipaddr, mask = req_ip.split('/')
self._commands.append(
'mlag-vip %s ip %s /%s force' % (req_group, ipaddr, mask))
if req_mac != current_mac:
self._commands.append(
'mlag system-mac %s' % (req_mac))
if self._commands:
self._commands.append('no mlag shutdown')
def _generate_no_mlag_vip_cmds(self):
if self._current_config.get('group_name'):
self._commands.append('no mlag-vip')
def check_declarative_intent_params(self, result):
if not result['changed']:
return
delay_interval = self._required_config.get('delay')
if delay_interval > 0:
time.sleep(delay_interval)
for cmd in ("show mlag-vip", ""):
show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False)
def main():
""" main entry point for module execution
"""
OnyxMLagVipModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,238 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_ospf
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage OSPF protocol on Mellanox ONYX network devices
description:
- This module provides declarative management and configuration of OSPF
protocol on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
ospf:
description:
- "OSPF instance number 1-65535"
required: true
router_id:
description:
- OSPF router ID. Required if I(state=present).
interfaces:
description:
- List of interfaces and areas. Required if I(state=present).
suboptions:
name:
description:
- Intrface name.
required: true
area:
description:
- OSPF area.
required: true
state:
description:
- OSPF state.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: add ospf router to interface
onyx_ospf:
ospf: 2
router_id: 192.168.8.2
interfaces:
- name: Eth1/1
- area: 0.0.0.0
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- router ospf 2
- router-id 192.168.8.2
- exit
- interface ethernet 1/1 ip ospf area 0.0.0.0
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxOspfModule(BaseOnyxModule):
OSPF_IF_REGEX = re.compile(
r'^(Loopback\d+|Eth\d+\/\d+|Vlan\d+|Po\d+)\s+(\S+).*')
OSPF_ROUTER_REGEX = re.compile(r'^Routing Process (\d+).*ID\s+(\S+).*')
@classmethod
def _get_element_spec(cls):
interface_spec = dict(
name=dict(required=True),
area=dict(required=True),
)
element_spec = dict(
ospf=dict(type='int', required=True),
router_id=dict(),
interfaces=dict(type='list', elements='dict',
options=interface_spec),
state=dict(choices=['present', 'absent'], default='present'),
)
return element_spec
def init_module(self):
""" Ansible module initialization
"""
element_spec = self._get_element_spec()
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def validate_ospf(self, value):
if value and not 1 <= int(value) <= 65535:
self._module.fail_json(msg='ospf id must be between 1 and 65535')
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(
ospf=module_params['ospf'],
router_id=module_params['router_id'],
state=module_params['state'],
)
interfaces = module_params['interfaces'] or list()
req_interfaces = self._required_config['interfaces'] = dict()
for interface_data in interfaces:
req_interfaces[interface_data['name']] = interface_data['area']
self.validate_param_values(self._required_config)
def _update_ospf_data(self, ospf_data):
match = self.OSPF_ROUTER_REGEX.match(ospf_data)
if match:
ospf_id = int(match.group(1))
router_id = match.group(2)
self._current_config['ospf'] = ospf_id
self._current_config['router_id'] = router_id
def _update_ospf_interfaces(self, ospf_interfaces):
interfaces = self._current_config['interfaces'] = dict()
lines = ospf_interfaces.split('\n')
for line in lines:
line = line.strip()
match = self.OSPF_IF_REGEX.match(line)
if match:
name = match.group(1)
area = match.group(2)
for prefix in ("Vlan", "Loopback"):
if name.startswith(prefix):
name = name.replace(prefix, prefix + ' ')
interfaces[name] = area
def _get_ospf_config(self, ospf_id):
cmd = 'show ip ospf %s | include Process' % ospf_id
return show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False)
def _get_ospf_interfaces_config(self, ospf_id):
cmd = 'show ip ospf interface %s brief' % ospf_id
return show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False)
def load_current_config(self):
# called in base class in run function
ospf_id = self._required_config['ospf']
self._current_config = dict()
ospf_data = self._get_ospf_config(ospf_id)
if ospf_data:
self._update_ospf_data(ospf_data)
ospf_interfaces = self._get_ospf_interfaces_config(ospf_id)
if ospf_interfaces:
self._update_ospf_interfaces(ospf_interfaces)
def _generate_no_ospf_commands(self):
req_ospf_id = self._required_config['ospf']
curr_ospf_id = self._current_config.get('ospf')
if curr_ospf_id == req_ospf_id:
cmd = 'no router ospf %s' % req_ospf_id
self._commands.append(cmd)
def _get_interface_command_name(self, if_name):
if if_name.startswith('Eth'):
return if_name.replace("Eth", "ethernet ")
if if_name.startswith('Po'):
return if_name.replace("Po", "port-channel ")
if if_name.startswith('Vlan'):
return if_name.replace("Vlan", "vlan")
if if_name.startswith('Loopback'):
return if_name.replace("Loopback", "loopback")
self._module.fail_json(
msg='invalid interface name: %s' % if_name)
def _get_interface_area_cmd(self, if_name, area):
interface_prefix = self._get_interface_command_name(if_name)
if area:
area_cmd = 'ip ospf area %s' % area
else:
area_cmd = 'no ip ospf area'
cmd = 'interface %s %s' % (interface_prefix, area_cmd)
return cmd
def _generate_ospf_commands(self):
req_router_id = self._required_config['router_id']
req_ospf_id = self._required_config['ospf']
curr_router_id = self._current_config.get('router_id')
curr_ospf_id = self._current_config.get('ospf')
if curr_ospf_id != req_ospf_id or req_router_id != curr_router_id:
cmd = 'router ospf %s' % req_ospf_id
self._commands.append(cmd)
if req_router_id != curr_router_id:
if req_router_id:
cmd = 'router-id %s' % req_router_id
else:
cmd = 'no router-id'
self._commands.append(cmd)
self._commands.append('exit')
req_interfaces = self._required_config['interfaces']
curr_interfaces = self._current_config.get('interfaces', dict())
for if_name, area in iteritems(req_interfaces):
curr_area = curr_interfaces.get(if_name)
if curr_area != area:
cmd = self._get_interface_area_cmd(if_name, area)
self._commands.append(cmd)
for if_name in curr_interfaces:
if if_name not in req_interfaces:
cmd = self._get_interface_area_cmd(if_name, None)
self._commands.append(cmd)
def generate_commands(self):
req_state = self._required_config['state']
if req_state == 'absent':
return self._generate_no_ospf_commands()
return self._generate_ospf_commands()
def main():
""" main entry point for module execution
"""
OnyxOspfModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,205 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_pfc_interface
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Manage priority flow control on ONYX network devices
description:
- This module provides declarative management of priority flow control (PFC)
on interfaces of Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
name:
description:
- Name of the interface PFC should be configured on.
aggregate:
description: List of interfaces PFC should be configured on.
purge:
description:
- Purge interfaces not defined in the aggregate parameter.
type: bool
default: false
state:
description:
- State of the PFC configuration.
default: enabled
choices: ['enabled', 'disabled']
"""
EXAMPLES = """
- name: configure PFC
onyx_pfc_interface:
name: Eth1/1
state: enabled
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/17 dcb priority-flow-control mode on
"""
from copy import deepcopy
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxPfcInterfaceModule(BaseOnyxModule):
PFC_IF_REGEX = re.compile(
r"^(Eth\d+\/\d+)|(Eth\d+\/\d+\/\d+)|(Po\d+)|(Mpo\d+)$")
_purge = False
@classmethod
def _get_element_spec(cls):
return dict(
name=dict(type='str'),
state=dict(default='enabled',
choices=['enabled', 'disabled']),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
purge=dict(default=False, type='bool'),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
self._purge = module_params.get('purge', False)
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
self._required_config.append(req_item)
else:
params = {
'name': module_params['name'],
'state': module_params['state'],
}
self.validate_param_values(params)
self._required_config.append(params)
def _create_if_pfc_data(self, if_name, if_pfc_data):
state = self.get_config_attr(if_pfc_data, "PFC oper")
state = state.lower()
return dict(
name=if_name,
state=state)
def _get_pfc_config(self):
return show_cmd(self._module, "show dcb priority-flow-control")
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
pfc_config = self._get_pfc_config()
if not pfc_config:
return
if 'Table 2' in pfc_config:
pfc_config = pfc_config['Table 2']
for if_name, if_pfc_data in iteritems(pfc_config):
match = self.PFC_IF_REGEX.match(if_name)
if not match:
continue
if if_pfc_data:
if_pfc_data = if_pfc_data[0]
self._current_config[if_name] = \
self._create_if_pfc_data(if_name, if_pfc_data)
def _get_interface_cmd_name(self, if_name):
if if_name.startswith('Eth'):
return if_name.replace("Eth", "ethernet ")
if if_name.startswith('Po'):
return if_name.replace("Po", "port-channel ")
if if_name.startswith('Mpo'):
return if_name.replace("Mpo", "mlag-port-channel ")
self._module.fail_json(
msg='invalid interface name: %s' % if_name)
def _add_if_pfc_commands(self, if_name, req_state):
cmd_prefix = "interface %s " % self._get_interface_cmd_name(if_name)
if req_state == 'disabled':
pfc_cmd = 'no dcb priority-flow-control mode force'
else:
pfc_cmd = 'dcb priority-flow-control mode on force'
self._commands.append(cmd_prefix + pfc_cmd)
def _gen_pfc_commands(self, if_name, curr_conf, req_state):
curr_state = curr_conf.get('state', 'disabled')
if curr_state != req_state:
self._add_if_pfc_commands(if_name, req_state)
def generate_commands(self):
req_interfaces = set()
for req_conf in self._required_config:
req_state = req_conf['state']
if_name = req_conf['name']
if req_state == 'enabled':
req_interfaces.add(if_name)
curr_conf = self._current_config.get(if_name, {})
self._gen_pfc_commands(if_name, curr_conf, req_state)
if self._purge:
for if_name, curr_conf in iteritems(self._current_config):
if if_name not in req_interfaces:
req_state = 'disabled'
self._gen_pfc_commands(if_name, curr_conf, req_state)
def main():
""" main entry point for module execution
"""
OnyxPfcInterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,185 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_protocol
version_added: "2.5"
author: "Samer Deeb (@samerd)"
short_description: Enables/Disables protocols on Mellanox ONYX network devices
description:
- This module provides a mechanism for enabling and disabling protocols
Mellanox on ONYX network devices.
notes:
- Tested on ONYX 3.6.4000
options:
mlag:
description: MLAG protocol
choices: ['enabled', 'disabled']
magp:
description: MAGP protocol
choices: ['enabled', 'disabled']
spanning_tree:
description: Spanning Tree support
choices: ['enabled', 'disabled']
dcb_pfc:
description: DCB priority flow control
choices: ['enabled', 'disabled']
igmp_snooping:
description: IP IGMP snooping
choices: ['enabled', 'disabled']
lacp:
description: LACP protocol
choices: ['enabled', 'disabled']
ip_l3:
description: IP L3 support
choices: ['enabled', 'disabled']
ip_routing:
description: IP routing support
choices: ['enabled', 'disabled']
lldp:
description: LLDP protocol
choices: ['enabled', 'disabled']
bgp:
description: BGP protocol
choices: ['enabled', 'disabled']
ospf:
description: OSPF protocol
choices: ['enabled', 'disabled']
"""
EXAMPLES = """
- name: enable protocols for MLAG
onyx_protocol:
lacp: enabled
spanning_tree: disabled
ip_routing: enabled
mlag: enabled
dcb_pfc: enabled
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- no spanning-tree
- protocol mlag
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxProtocolModule(BaseOnyxModule):
PROTOCOL_MAPPING = dict(
mlag=dict(name="mlag", enable="protocol mlag",
disable="no protocol mlag"),
magp=dict(name="magp", enable="protocol magp",
disable="no protocol magp"),
spanning_tree=dict(name="spanning-tree", enable="spanning-tree",
disable="no spanning-tree"),
dcb_pfc=dict(name="priority-flow-control",
enable="dcb priority-flow-control enable force",
disable="no dcb priority-flow-control enable force"),
igmp_snooping=dict(name="igmp-snooping", enable="ip igmp snooping",
disable="no ip igmp snooping"),
lacp=dict(name="lacp", enable="lacp", disable="no lacp"),
ip_l3=dict(name="IP L3", enable="ip l3",
disable="no ip l3"),
ip_routing=dict(name="IP routing", enable="ip routing",
disable="no ip routing"),
lldp=dict(name="lldp", enable="lldp", disable="no lldp"),
bgp=dict(name="bgp", enable="protocol bgp", disable="no protocol bgp"),
ospf=dict(name="ospf", enable="protocol ospf",
disable="no protocol ospf"),
)
@classmethod
def _get_element_spec(cls):
element_spec = dict()
for protocol in cls.PROTOCOL_MAPPING:
element_spec[protocol] = dict(choices=['enabled', 'disabled'])
return element_spec
def init_module(self):
""" Ansible module initialization
"""
element_spec = self._get_element_spec()
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
def get_required_config(self):
self._required_config = dict()
module_params = self._module.params
for key, val in iteritems(module_params):
if key in self.PROTOCOL_MAPPING and val is not None:
self._required_config[key] = val
def _get_protocols(self):
return show_cmd(self._module, "show protocols")
def _get_ip_routing(self):
return show_cmd(self._module, 'show ip routing | include "IP routing"',
json_fmt=False)
def load_current_config(self):
self._current_config = dict()
protocols_config = self._get_protocols()
if not protocols_config:
protocols_config = dict()
ip_config = self._get_ip_routing()
if ip_config:
lines = ip_config.split('\n')
for line in lines:
line = line.strip()
line_attr = line.split(':')
if len(line_attr) == 2:
attr = line_attr[0].strip()
val = line_attr[1].strip()
protocols_config[attr] = val
for protocol, protocol_metadata in iteritems(self.PROTOCOL_MAPPING):
protocol_json_attr = protocol_metadata['name']
val = protocols_config.get(protocol_json_attr, 'disabled')
if val not in ('enabled', 'disabled'):
val = 'enabled'
self._current_config[protocol] = val
def generate_commands(self):
for protocol, req_val in iteritems(self._required_config):
protocol_metadata = self.PROTOCOL_MAPPING[protocol]
curr_val = self._current_config.get(protocol, 'disabled')
if curr_val != req_val:
if req_val == 'disabled':
command = protocol_metadata['disable']
else:
command = protocol_metadata['enable']
self._commands.append(command)
def main():
""" main entry point for module execution
"""
OnyxProtocolModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,201 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: onyx_vlan
version_added: "2.5"
author: "Samer Deeb (@samerd) Alex Tabachnik (@atabachnik)"
short_description: Manage VLANs on Mellanox ONYX network devices
description:
- This module provides declarative management of VLANs
on Mellanox ONYX network devices.
options:
name:
description:
- Name of the VLAN.
vlan_id:
description:
- ID of the VLAN.
aggregate:
description: List of VLANs definitions.
purge:
description:
- Purge VLANs not defined in the I(aggregate) parameter.
default: no
state:
description:
- State of the VLAN configuration.
default: present
choices: ['present', 'absent']
"""
EXAMPLES = """
- name: configure VLAN ID and name
onyx_vlan:
vlan_id: 20
name: test-vlan
- name: remove configuration
onyx_vlan:
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- vlan 20
- name test-vlan
- exit
"""
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible.module_utils.network.onyx.onyx import show_cmd
class OnyxVlanModule(BaseOnyxModule):
_purge = False
@classmethod
def _get_element_spec(cls):
return dict(
vlan_id=dict(type='int'),
name=dict(type='str'),
state=dict(default='present', choices=['present', 'absent']),
)
@classmethod
def _get_aggregate_spec(cls, element_spec):
aggregate_spec = deepcopy(element_spec)
aggregate_spec['vlan_id'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
return aggregate_spec
def init_module(self):
""" module initialization
"""
element_spec = self._get_element_spec()
aggregate_spec = self._get_aggregate_spec(element_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec),
purge=dict(default=False, type='bool'),
)
argument_spec.update(element_spec)
required_one_of = [['vlan_id', 'aggregate']]
mutually_exclusive = [['vlan_id', 'aggregate']]
self._module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
def validate_vlan_id(self, value):
if value and not 1 <= int(value) <= 4094:
self._module.fail_json(msg='vlan id must be between 1 and 4094')
def get_required_config(self):
self._required_config = list()
module_params = self._module.params
aggregate = module_params.get('aggregate')
self._purge = module_params.get('purge', False)
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module_params[key]
self.validate_param_values(item, item)
req_item = item.copy()
req_item['vlan_id'] = int(req_item['vlan_id'])
self._required_config.append(req_item)
else:
params = {
'vlan_id': module_params['vlan_id'],
'name': module_params['name'],
'state': module_params['state'],
}
self.validate_param_values(params)
self._required_config.append(params)
def _create_vlan_data(self, vlan_id, vlan_data):
return {
'vlan_id': vlan_id,
'name': self.get_config_attr(vlan_data, 'Name')
}
def _get_vlan_config(self):
return show_cmd(self._module, "show vlan")
def load_current_config(self):
# called in base class in run function
self._current_config = dict()
vlan_config = self._get_vlan_config()
if not vlan_config:
return
for vlan_id, vlan_data in iteritems(vlan_config):
try:
vlan_id = int(vlan_id)
except ValueError:
continue
self._current_config[vlan_id] = \
self._create_vlan_data(vlan_id, vlan_data)
def generate_commands(self):
req_vlans = set()
for req_conf in self._required_config:
state = req_conf['state']
vlan_id = req_conf['vlan_id']
if state == 'absent':
if vlan_id in self._current_config:
self._commands.append('no vlan %s' % vlan_id)
else:
req_vlans.add(vlan_id)
self._generate_vlan_commands(vlan_id, req_conf)
if self._purge:
for vlan_id in self._current_config:
if vlan_id not in req_vlans:
self._commands.append('no vlan %s' % vlan_id)
def _generate_vlan_commands(self, vlan_id, req_conf):
curr_vlan = self._current_config.get(vlan_id, {})
if not curr_vlan:
self._commands.append("vlan %s" % vlan_id)
self._commands.append("exit")
req_name = req_conf['name']
curr_name = curr_vlan.get('name')
if req_name:
if req_name != curr_name:
self._commands.append("vlan %s name %s" % (vlan_id, req_name))
elif req_name is not None:
if curr_name:
self._commands.append("vlan %s no name" % vlan_id)
def main():
""" main entry point for module execution
"""
OnyxVlanModule.main()
if __name__ == '__main__':
main()