Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
#
# (c) 2017, Red Hat, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
class ActionModule(ActionNetworkModule):
def run(self, tmp=None, task_vars=None):
del tmp # tmp no longer has any effect
self._config_module = True
return super(ActionModule, self).run(task_vars=task_vars)

View File

@@ -0,0 +1,78 @@
#
# (c) 2017 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
---
cliconf: onyx
short_description: Use onyx cliconf to run command on Mellanox ONYX platform
description:
- This onyx plugin provides low level abstraction apis for
sending and receiving CLI commands from Mellanox ONYX network devices.
version_added: "2.5"
"""
import json
from itertools import chain
from ansible.module_utils._text import to_text
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
class Cliconf(CliconfBase):
def get_device_info(self):
device_info = {}
reply = self.get('show version | json-print')
data = json.loads(reply)
device_info['network_os'] = data['Product name']
device_info['network_os_version'] = data['Product release']
device_info['network_os_version_summary'] = data['Version summary']
device_info['network_os_model'] = data['Product model']
reply = self.get('show hosts | include Hostname')
data = to_text(reply, errors='surrogate_or_strict').strip()
hostname = data.split(':')[1]
hostname = hostname.strip()
device_info['network_os_hostname'] = hostname
return device_info
@enable_mode
def get_config(self, source='running', format='text', flags=None):
if source not in ('running',):
return self.invalid_params("fetching configuration from %s is not supported" % source)
cmd = 'show running-config'
return self.send_command(cmd)
@enable_mode
def edit_config(self, command):
for cmd in chain(['configure terminal'], to_list(command), ['exit']):
self.send_command(cmd)
def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False):
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# 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
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r'''
options:
provider:
description:
- A dict object containing connection details.
type: dict
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
type: str
required: true
port:
description:
- Specifies the port to use when building the connection to the remote device.
type: int
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead.
type: str
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead.
type: str
timeout:
description:
- Specifies the timeout in seconds for communicating with the network device
for either connecting or sending commands. If the timeout is
exceeded before the operation is completed, the module will error.
type: int
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not specified
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
will be used instead.
type: path
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the value
is not specified in the task, the value of environment variable
C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: no
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead.
type: str
'''

View File

@@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
#
# (c) 2017, Ansible by Red Hat, inc
#
# This file is part of Ansible by Red Hat
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection, ConnectionError
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, EntityCollection
_DEVICE_CONFIGS = {}
_CONNECTION = None
_COMMAND_SPEC = {
'command': dict(key=True),
'prompt': dict(),
'answer': dict()
}
def get_connection(module):
global _CONNECTION
if _CONNECTION:
return _CONNECTION
_CONNECTION = Connection(module._socket_path)
return _CONNECTION
def to_commands(module, commands):
if not isinstance(commands, list):
raise AssertionError('argument must be of type <list>')
transform = EntityCollection(module, _COMMAND_SPEC)
commands = transform(commands)
return commands
def run_commands(module, commands, check_rc=True):
connection = get_connection(module)
commands = to_commands(module, to_list(commands))
responses = list()
for cmd in commands:
out = connection.get(**cmd)
responses.append(to_text(out, errors='surrogate_then_replace'))
return responses
def get_config(module, source='running'):
conn = get_connection(module)
out = conn.get_config(source)
cfg = to_text(out, errors='surrogate_then_replace').strip()
return cfg
def load_config(module, config):
try:
conn = get_connection(module)
conn.edit_config(config)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc))
def _parse_json_output(out):
out_list = out.split('\n')
first_index = 0
opening_char = None
lines_count = len(out_list)
while first_index < lines_count:
first_line = out_list[first_index].strip()
if not first_line or first_line[0] not in ("[", "{"):
first_index += 1
continue
opening_char = first_line[0]
break
if not opening_char:
return "null"
closing_char = ']' if opening_char == '[' else '}'
last_index = lines_count - 1
found = False
while last_index > first_index:
last_line = out_list[last_index].strip()
if not last_line or last_line[0] != closing_char:
last_index -= 1
continue
found = True
break
if not found:
return opening_char + closing_char
return "".join(out_list[first_index:last_index + 1])
def show_cmd(module, cmd, json_fmt=True, fail_on_error=True):
if json_fmt:
cmd += " | json-print"
conn = get_connection(module)
command_obj = to_commands(module, to_list(cmd))[0]
try:
out = conn.get(**command_obj)
except ConnectionError:
if fail_on_error:
raise
return None
if json_fmt:
out = _parse_json_output(out)
try:
cfg = json.loads(out)
except ValueError:
module.fail_json(
msg="got invalid json",
stderr=to_text(out, errors='surrogate_then_replace'))
else:
cfg = to_text(out, errors='surrogate_then_replace').strip()
return cfg
def get_interfaces_config(module, interface_type, flags=None, json_fmt=True):
cmd = "show interfaces %s" % interface_type
if flags:
cmd += " %s" % flags
return show_cmd(module, cmd, json_fmt)
def get_bgp_summary(module):
cmd = "show running-config protocol bgp"
return show_cmd(module, cmd, json_fmt=False, fail_on_error=False)
def get_capabilities(module):
"""Returns platform info of the remove device
"""
if hasattr(module, '_capabilities'):
return module._capabilities
connection = get_connection(module)
try:
capabilities = connection.get_capabilities()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
module._capabilities = json.loads(capabilities)
return module._capabilities
class BaseOnyxModule(object):
ONYX_API_VERSION = "3.6.6000"
def __init__(self):
self._module = None
self._commands = list()
self._current_config = None
self._required_config = None
self._os_version = None
def init_module(self):
pass
def load_current_config(self):
pass
def get_required_config(self):
pass
def _get_os_version(self):
capabilities = get_capabilities(self._module)
device_info = capabilities['device_info']
return device_info['network_os_version']
# pylint: disable=unused-argument
def check_declarative_intent_params(self, result):
return None
def _validate_key(self, param, key):
validator = getattr(self, 'validate_%s' % key)
if callable(validator):
validator(param.get(key))
def validate_param_values(self, obj, param=None):
if param is None:
param = self._module.params
for key in obj:
# validate the param value (if validator func exists)
try:
self._validate_key(param, key)
except AttributeError:
pass
@classmethod
def get_config_attr(cls, item, arg):
return item.get(arg)
@classmethod
def get_mtu(cls, item):
mtu = cls.get_config_attr(item, "MTU")
mtu_parts = mtu.split()
try:
return int(mtu_parts[0])
except ValueError:
return None
def _validate_range(self, attr_name, min_val, max_val, value):
if value is None:
return True
if not min_val <= int(value) <= max_val:
msg = '%s must be between %s and %s' % (
attr_name, min_val, max_val)
self._module.fail_json(msg=msg)
def validate_mtu(self, value):
self._validate_range('mtu', 1500, 9612, value)
def generate_commands(self):
pass
def run(self):
self.init_module()
result = {'changed': False}
self.get_required_config()
self.load_current_config()
self.generate_commands()
result['commands'] = self._commands
if self._commands:
if not self._module.check_mode:
load_config(self._module, self._commands)
result['changed'] = True
failed_conditions = self.check_declarative_intent_params(result)
if failed_conditions:
msg = 'One or more conditional statements have not been satisfied'
self._module.fail_json(msg=msg,
failed_conditions=failed_conditions)
self._module.exit_json(**result)
@classmethod
def main(cls):
app = cls()
app.run()

View File

@@ -0,0 +1,157 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_aaa
version_added: '0.2.0'
author: "Sara Touqan (@sarato)"
short_description: Configures AAA parameters
description:
- This module provides declarative management of AAA protocol params
on Mellanox ONYX network devices.
options:
tacacs_accounting_enabled:
description:
- Configures accounting settings.
type: bool
auth_default_user:
description:
- Sets local user default mapping.
type: str
choices: ['admin', 'monitor']
auth_order:
description:
- Sets the order on how to handle remote to local user mappings.
type: str
choices: ['local-only', 'remote-first', 'remote-only']
auth_fallback_enabled:
description:
- Enables/Disables fallback server-err option.
type: bool
'''
EXAMPLES = """
- name: Configures aaa
onyx_aaa:
tacacs_accounting_enabled: yes
auth_default_user: monitor
auth_order: local-only
auth_fallback_enabled: false
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- aaa accounting changes default stop-only tacacs+
- no aaa accounting changes default stop-only tacacs+
- aaa authorization map default-user <user>
- aaa authorization map order <order>
- aaa authorization map fallback server-err
- no aaa authorization map fallback server-err
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxAAAModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
element_spec = dict(
tacacs_accounting_enabled=dict(type='bool'),
auth_default_user=dict(type='str', choices=['admin', 'monitor']),
auth_order=dict(type='str', choices=['local-only', 'remote-first', 'remote-only']),
auth_fallback_enabled=dict(type='bool')
)
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(module_params)
self.validate_param_values(self._required_config)
def _set_aaa_config(self, all_aaa_config):
aaa_config = all_aaa_config[0]
self._current_config['auth_default_user'] = aaa_config.get("Default User")
self._current_config['auth_order'] = aaa_config.get("Map Order")
auth_fallback_enabled = aaa_config.get("Fallback on server-err")
if auth_fallback_enabled == "yes":
self._current_config['auth_fallback_enabled'] = True
else:
self._current_config['auth_fallback_enabled'] = False
aaa_config_2 = all_aaa_config[2]
accounting_message = aaa_config_2.get("message")
if accounting_message == "No accounting methods configured.":
self._current_config['tacacs_accounting_enabled'] = False
else:
self._current_config['tacacs_accounting_enabled'] = True
def _show_aaa_config(self):
cmd = "show aaa"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
aaa_config = self._show_aaa_config()
if aaa_config:
self._set_aaa_config(aaa_config)
def generate_commands(self):
tacacs_accounting_enabled = self._required_config.get("tacacs_accounting_enabled")
if tacacs_accounting_enabled is not None:
current_accounting_enabled = self._current_config.get("tacacs_accounting_enabled")
if current_accounting_enabled != tacacs_accounting_enabled:
if tacacs_accounting_enabled is True:
self._commands.append('aaa accounting changes default stop-only tacacs+')
else:
self._commands.append('no aaa accounting changes default stop-only tacacs+')
auth_default_user = self._required_config.get("auth_default_user")
if auth_default_user is not None:
current_user = self._current_config.get("auth_default_user")
if current_user != auth_default_user:
self._commands.append('aaa authorization map default-user {0}' .format(auth_default_user))
auth_order = self._required_config.get("auth_order")
if auth_order is not None:
current_order = self._current_config.get("auth_order")
if current_order != auth_order:
self._commands.append('aaa authorization map order {0}' .format(auth_order))
auth_fallback_enabled = self._required_config.get("auth_fallback_enabled")
if auth_fallback_enabled is not None:
current_fallback = self._current_config.get("auth_fallback_enabled")
if current_fallback != auth_fallback_enabled:
if auth_fallback_enabled is True:
self._commands.append('aaa authorization map fallback server-err')
else:
self._commands.append('no aaa authorization map fallback server-err')
def main():
""" main entry point for module execution
"""
OnyxAAAModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,241 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_bfd
version_added: '0.2.0'
author: "Sara Touqan (@sarato)"
short_description: Configures BFD parameters
description:
- This module provides declarative management of BFD protocol params
on Mellanox ONYX network devices.
options:
shutdown:
description:
- Administratively shut down BFD protection.
type: bool
vrf:
description:
- Specifys the vrf name.
type: str
interval_min_rx:
description:
- Minimum desired receive rate, should be between 50 and 6000.
type: int
interval_multiplier:
description:
- Desired detection multiplier, should be between 3 and 50.
type: int
interval_transmit_rate:
description:
- Minimum desired transmit rate, should be between 50 and 60000.
type: int
iproute_network_prefix:
description:
- Configures the ip route network prefix, e.g 1.1.1.1.
type: str
iproute_mask_length:
description:
- Configures the mask length of the ip route network prefix, e.g 24.
type: int
iproute_next_hop:
description:
- Configures the ip route next hop, e.g 2.2.2.2.
type: str
'''
EXAMPLES = """
- name: Configures bfd
onyx_bfd:
shutdown: yes
vrf: 5
interval_min_rx: 55
interval_multiplier: 8
interval_transmit_rate: 88
iproute_network_prefix: 1.1.1.0
iproute_mask_length: 24
iproute_next_hop: 3.2.2.2
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- ip bfd shutdown
- no ip bfd shutdown
- ip bfd shutdown vrf <vrf_name>
- no ip bfd shutdown vrf <vrf_name>
- ip bfd vrf <vrf_name> interval min-rx <min_rx> multiplier <multiplier> transmit-rate <transmit_rate> force
- ip bfd interval min-rx <min_rx> multiplier <multiplier> transmit-rate <transmit_rate> force
- ip route vrf <vrf_name> <network_prefix>/<mask_length> <next_hop> bfd
- ip route <network_prefix>/<mask_length> <next_hop> bfd
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxBFDModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
element_spec = dict(
shutdown=dict(type='bool'),
vrf=dict(type='str'),
interval_min_rx=dict(type='int'),
interval_multiplier=dict(type='int'),
interval_transmit_rate=dict(type='int'),
iproute_network_prefix=dict(type='str'),
iproute_mask_length=dict(type='int'),
iproute_next_hop=dict(type='str'),
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_together=[
['interval_min_rx', 'interval_multiplier', 'interval_transmit_rate'],
['iproute_network_prefix', 'iproute_mask_length', 'iproute_next_hop']])
def validate_bfd_interval_values(self):
interval_min_rx = self._required_config.get('interval_min_rx')
if interval_min_rx:
if ((interval_min_rx < 50) or (interval_min_rx > 6000)):
self._module.fail_json(msg='Receive interval should be between 50 and 6000.')
interval_multiplier = self._required_config.get('interval_multiplier')
if interval_multiplier:
if ((interval_multiplier < 3) or (interval_multiplier > 50)):
self._module.fail_json(msg='Multiplier should be between 3 and 50.')
interval_transmit_rate = self._required_config.get('interval_transmit_rate')
if interval_transmit_rate:
if ((interval_transmit_rate < 50) or (interval_transmit_rate > 60000)):
self._module.fail_json(msg='Transmit interval should be between 50 and 60000.')
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(module_params)
self.validate_param_values(self._required_config)
self.validate_bfd_interval_values()
def _set_bfd_config(self, bfd_config):
curr_config_arr = []
bfd_config = bfd_config.get('Lines')
if bfd_config is None:
return
for runn_config in bfd_config:
curr_config_arr.append(runn_config.strip())
if 'ip bfd shutdown vrf default' in curr_config_arr:
self._current_config['bfd_shutdown'] = True
else:
self._current_config['bfd_shutdown'] = False
self._current_config['curr_config_arr'] = curr_config_arr
def _show_bfd_config(self):
cmd = "show running-config | include bfd"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
bfd_config = self._show_bfd_config()
if bfd_config:
self._set_bfd_config(bfd_config)
def generate_shutdown_commands(self, curr_config_arr):
shutdown_enabled = self._required_config.get('shutdown')
vrf_name = self._required_config.get('vrf')
current_shutdown = self._current_config.get("bfd_shutdown")
if shutdown_enabled is not None:
if vrf_name is not None:
if curr_config_arr is not None:
if ('ip bfd shutdown vrf {0}' .format(vrf_name)) not in curr_config_arr:
if shutdown_enabled is True:
self._commands.append('ip bfd shutdown vrf {0}' .format(vrf_name))
else:
if shutdown_enabled is False:
self._commands.append('no ip bfd shutdown vrf {0}' .format(vrf_name))
else:
if ((current_shutdown is not None and (current_shutdown != shutdown_enabled)) or (current_shutdown is None)):
if shutdown_enabled is True:
self._commands.append('ip bfd shutdown')
else:
self._commands.append('no ip bfd shutdown')
def generate_interval_commands(self, curr_config_arr):
interval_min_rx = self._required_config.get('interval_min_rx')
interval_multiplier = self._required_config.get('interval_multiplier')
interval_transmit_rate = self._required_config.get('interval_transmit_rate')
vrf_name = self._required_config.get('vrf')
if ((interval_min_rx is not None) and (interval_multiplier is not None) and (interval_transmit_rate is not None)):
if vrf_name is not None:
if curr_config_arr is not None:
if ((('ip bfd vrf {0} interval transmit-rate {1} force' .format(vrf_name, interval_transmit_rate)) not in curr_config_arr) or
(('ip bfd vrf {0} interval min-rx {1} force' .format(vrf_name, interval_min_rx)) not in curr_config_arr) or
(('ip bfd vrf {0} interval multiplier {1} force' .format(vrf_name, interval_multiplier)) not in curr_config_arr)):
self._commands.append('ip bfd vrf {0} interval min-rx {1} multiplier {2} transmit-rate {3} force'
.format(vrf_name, interval_min_rx, interval_multiplier, interval_transmit_rate))
else:
self._commands.append('ip bfd vrf {0} interval min-rx {1} multiplier {2} transmit-rate {3} force'
.format(vrf_name, interval_min_rx, interval_multiplier, interval_transmit_rate))
else:
if curr_config_arr is not None:
if ((('ip bfd vrf default interval transmit-rate {0} force' .format(interval_transmit_rate)) not in curr_config_arr) or
(('ip bfd vrf default interval min-rx {0} force' .format(interval_min_rx)) not in curr_config_arr) or
(('ip bfd vrf default interval multiplier {0} force' .format(interval_multiplier)) not in curr_config_arr)):
self._commands.append('ip bfd interval min-rx {0} multiplier {1} transmit-rate {2} force'
.format(interval_min_rx, interval_multiplier, interval_transmit_rate))
else:
self._commands.append('ip bfd interval min-rx {0} multiplier {1} transmit-rate {2} force'
.format(interval_min_rx, interval_multiplier, interval_transmit_rate))
def generate_iproute_commands(self, curr_config_arr):
iproute_network_prefix = self._required_config.get('iproute_network_prefix')
iproute_mask_length = self._required_config.get('iproute_mask_length')
iproute_next_hop = self._required_config.get('iproute_next_hop')
vrf_name = self._required_config.get('vrf')
if ((iproute_network_prefix is not None) and (iproute_mask_length is not None) and
(iproute_next_hop is not None)):
if vrf_name is not None:
if curr_config_arr is not None:
if ('ip route vrf {0} {1}/{2} {3} bfd' .format(vrf_name, iproute_network_prefix,
iproute_mask_length, iproute_next_hop)) not in curr_config_arr:
self._commands.append('ip route vrf {0} {1} /{2} {3} bfd'
.format(vrf_name, iproute_network_prefix, iproute_mask_length, iproute_next_hop))
else:
self._commands.append('ip route vrf {0} {1} /{2} {3} bfd' .format(vrf_name, iproute_network_prefix, iproute_mask_length, iproute_next_hop))
else:
if curr_config_arr is not None:
if ('ip route vrf default {0}/{1} {2} bfd' .format(iproute_network_prefix,
iproute_mask_length, iproute_next_hop)) not in curr_config_arr:
self._commands.append('ip route {0} /{1} {2} bfd' .format(iproute_network_prefix, iproute_mask_length, iproute_next_hop))
else:
self._commands.append('ip route {0} /{1} {2} bfd' .format(iproute_network_prefix, iproute_mask_length, iproute_next_hop))
def generate_commands(self):
curr_config_arr = self._current_config.get("curr_config_arr")
self.generate_shutdown_commands(curr_config_arr)
self.generate_interval_commands(curr_config_arr)
self.generate_iproute_commands(curr_config_arr)
def main():
""" main entry point for module execution
"""
OnyxBFDModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,446 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_bgp
author: "Samer Deeb (@samerd), Anas Badaha (@anasb)"
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.
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
multihop:
description:
- multihop number.
networks:
description:
- List of advertised networks.
fast_external_fallover:
description:
- will configure fast_external_fallover when it is True.
type: bool
max_paths:
description:
- Maximum bgp paths.
ecmp_bestpath:
description:
- Enables ECMP across AS paths.
type: bool
evpn:
description:
- Configure evpn peer-group.
type: bool
vrf:
description:
- vrf name.
state:
description:
- BGP state.
default: present
choices: ['present', 'absent']
purge:
description:
- will remove all neighbors when it is True.
type: bool
default: false
'''
EXAMPLES = """
- name: Configure bgp
onyx_bgp:
as_number: 320
router_id: 10.3.3.3
neighbors:
- remote_as: 321
neighbor: 10.3.3.4
- remote_as: 322
neighbor: 10.3.3.5
multihop: 250
purge: True
state: present
networks:
- 172.16.1.0/24
vrf: default
evpn: yes
fast_external_fallover: yes
max_paths: 32
ecmp_bestpath: yes
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- router bgp 320 vrf default
- exit
- router bgp 320 router-id 10.3.3.3 force
- router bgp 320 vrf default bgp fast-external-fallover
- router bgp 320 vrf default maximum-paths 32
- router bgp 320 vrf default bestpath as-path multipath-relax force
- router bgp 320 vrf default neighbor evpn peer-group
- router bgp 320 vrf default neighbor evpn send-community extended
- router bgp 320 vrf default address-family l2vpn-evpn neighbor evpn next-hop-unchanged
- router bgp 320 vrf default address-family l2vpn-evpn neighbor evpn activate
- router bgp 320 vrf default address-family l2vpn-evpn auto-create
- router bgp 320 vrf default neighbor 10.3.3.4 remote-as 321
- router bgp 320 vrf default neighbor 10.3.3.4 ebgp-multihop 250
- router bgp 320 vrf default neighbor 10.3.3.5 remote-as 322
- router bgp 320 vrf default network 172.16.1.0 /24
"""
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import get_bgp_summary
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxBgpModule(BaseOnyxModule):
LOCAL_AS_REGEX = re.compile(r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+).*')
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+(\d+).*')
NEIGHBOR_MULTIHOP_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+).*neighbor\s+(\S+)\s+ebgp\-multihop\s+(\d+).*')
NETWORK_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+).*network\s+(\S+)\s+(\S+).*')
FAST_EXTERNAL_FALLOVER_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+bgp fast\-external\-fallover.*')
MAX_PATHS_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+maximum\-paths\s+(\d+).*')
ECMP_BESTPATH_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+bestpath as\-path multipath\-relax.*')
NEIGHBOR_EVPN_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+neighbor\s+(\S+)\s+peer\-group evpn.*')
EVPN_PEER_GROUP_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+neighbor evpn peer\-group.*')
EVPN_SEND_COMMUNITY_EXTENDED_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+neighbor evpn send-community extended.*')
EVPN_NEXT_HOP_UNCHANGED_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+address\-family l2vpn\-evpn neighbor evpn next\-hop-unchanged.*')
EVPN_ACTIVATE_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+address-family l2vpn\-evpn neighbor evpn activate.*')
EVPN_AUTO_CREATE_REGEX = re.compile(
r'^\s.*router bgp\s+(\d+)\s+vrf\s+(\S+)\s+address-family l2vpn\-evpn auto-create.*')
_purge = False
EVPN_PEER_GROUP_ATTR = "evpn_peer_group"
EVPN_SEND_COMMUNITY_EXTENDED_ATTR = "evpn_send_community_extended"
EVPN_NEXT_HOP_UNCHANGED_ATTR = "evpn_next_hop_unchanged"
EVPN_ACTIVATE_ATTR = "evpn_activate"
EVPN_AUTO_CREATE_ATTR = "evpn_auto_create"
EVPN_PEER_GROUP_CMD = "router bgp %s vrf %s neighbor evpn peer-group"
EVPN_SEND_COMMUNITY_EXTENDED_CMD = "router bgp %s vrf %s neighbor evpn send-community extended"
EVPN_NEXT_HOP_UNCHANGED_CMD = "router bgp %s vrf %s address-family l2vpn-evpn neighbor evpn next-hop-unchanged"
EVPN_ACTIVATE_CMD = "router bgp %s vrf %s address-family l2vpn-evpn neighbor evpn activate"
EVPN_AUTO_CREATE_CMD = "router bgp %s vrf %s address-family l2vpn-evpn auto-create"
EVPN_ENABLE_ATTRS = [EVPN_PEER_GROUP_ATTR, EVPN_SEND_COMMUNITY_EXTENDED_ATTR,
EVPN_NEXT_HOP_UNCHANGED_ATTR, EVPN_ACTIVATE_ATTR, EVPN_AUTO_CREATE_ATTR]
EVPN_DISABLE_ATTRS = [EVPN_PEER_GROUP_ATTR, EVPN_AUTO_CREATE_ATTR]
EVPN_COMMANDS_REGEX_MAPPER = {
EVPN_PEER_GROUP_ATTR: (EVPN_PEER_GROUP_REGEX, EVPN_PEER_GROUP_CMD),
EVPN_SEND_COMMUNITY_EXTENDED_ATTR: (EVPN_SEND_COMMUNITY_EXTENDED_REGEX,
EVPN_SEND_COMMUNITY_EXTENDED_CMD),
EVPN_NEXT_HOP_UNCHANGED_ATTR: (EVPN_NEXT_HOP_UNCHANGED_REGEX,
EVPN_NEXT_HOP_UNCHANGED_CMD),
EVPN_ACTIVATE_ATTR: (EVPN_ACTIVATE_REGEX, EVPN_ACTIVATE_CMD),
EVPN_AUTO_CREATE_ATTR: (EVPN_AUTO_CREATE_REGEX, EVPN_AUTO_CREATE_CMD)
}
def init_module(self):
""" initialize module
"""
neighbor_spec = dict(
remote_as=dict(type='int', required=True),
neighbor=dict(required=True),
multihop=dict(type='int')
)
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'),
purge=dict(default=False, type='bool'),
vrf=dict(),
fast_external_fallover=dict(type='bool'),
max_paths=dict(type='int'),
ecmp_bestpath=dict(type='bool'),
evpn=dict(type='bool')
)
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(module_params)
self._purge = self._required_config.get('purge', False)
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
self._current_config['fast_external_fallover'] = False
self._current_config['ecmp_bestpath'] = False
self._current_config[self.EVPN_PEER_GROUP_ATTR] = False
self._current_config[self.EVPN_SEND_COMMUNITY_EXTENDED_ATTR] = False
self._current_config[self.EVPN_NEXT_HOP_UNCHANGED_ATTR] = False
self._current_config[self.EVPN_AUTO_CREATE_ATTR] = False
self._current_config[self.EVPN_ACTIVATE_ATTR] = False
neighbors = self._current_config['neighbors'] = dict()
networks = self._current_config['networks'] = list()
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))
self._current_config['vrf'] = match.group(2)
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:
neighbor = neighbors.setdefault(match.group(2), dict())
neighbor['remote_as'] = int(match.group(3))
continue
match = self.NEIGHBOR_MULTIHOP_REGEX.match(line)
if match:
neighbor = neighbors.setdefault(match.group(2), dict())
neighbor["multihop"] = int(match.group(3))
continue
match = self.NEIGHBOR_EVPN_REGEX.match(line)
if match:
neighbor = neighbors.setdefault(match.group(3), dict())
neighbor["evpn"] = True
continue
match = self.NETWORK_REGEX.match(line)
if match:
network = match.group(2) + match.group(3)
networks.append(network)
continue
match = self.FAST_EXTERNAL_FALLOVER_REGEX.match(line)
if match:
self._current_config['fast_external_fallover'] = True
continue
match = self.ECMP_BESTPATH_REGEX.match(line)
if match:
self._current_config['ecmp_bestpath'] = True
continue
match = self.MAX_PATHS_REGEX.match(line)
if match:
self._current_config['max_paths'] = int(match.group(3))
continue
for key, value in iteritems(self.EVPN_COMMANDS_REGEX_MAPPER):
match = value[0].match(line)
if match:
self._current_config[key] = True
break
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):
vrf = self._required_config.get('vrf')
if vrf is None:
vrf = "default"
as_number = self._required_config['as_number']
curr_as_num = self._current_config.get('as_number')
curr_vrf = self._current_config.get("vrf")
bgp_removed = False
if curr_as_num != as_number or vrf != curr_vrf:
if curr_as_num:
self._commands.append('no router bgp %d vrf %s' % (curr_as_num, curr_vrf))
bgp_removed = True
self._commands.append('router bgp %d vrf %s' % (as_number, vrf))
self._commands.append('exit')
req_router_id = self._required_config.get('router_id')
if req_router_id is not None:
curr_route_id = self._current_config.get('router_id')
if bgp_removed or req_router_id != curr_route_id:
self._commands.append('router bgp %d vrf %s router-id %s force' % (as_number, vrf, req_router_id))
fast_external_fallover = self._required_config.get('fast_external_fallover')
if fast_external_fallover is not None:
current_fast_external_fallover = self._current_config.get('fast_external_fallover')
if fast_external_fallover and (bgp_removed or fast_external_fallover != current_fast_external_fallover):
self._commands.append('router bgp %d vrf %s bgp fast-external-fallover' % (as_number, vrf))
elif not fast_external_fallover and (bgp_removed or fast_external_fallover != current_fast_external_fallover):
self._commands.append('router bgp %d vrf %s no bgp fast-external-fallover' % (as_number, vrf))
max_paths = self._required_config.get('max_paths')
if max_paths is not None:
current_max_paths = self._current_config.get('max_paths')
if bgp_removed or max_paths != current_max_paths:
self._commands.append('router bgp %d vrf %s maximum-paths %s' % (as_number, vrf, max_paths))
ecmp_bestpath = self._required_config.get('ecmp_bestpath')
if ecmp_bestpath is not None:
current_ecmp_bestpath = self._current_config.get('ecmp_bestpath')
if ecmp_bestpath and (bgp_removed or ecmp_bestpath != current_ecmp_bestpath):
self._commands.append('router bgp %d vrf %s bestpath as-path multipath-relax force' % (as_number, vrf))
elif not ecmp_bestpath and (bgp_removed or ecmp_bestpath != current_ecmp_bestpath):
self._commands.append('router bgp %d vrf %s no bestpath as-path multipath-relax force' % (as_number, vrf))
evpn = self._required_config.get('evpn')
if evpn is not None:
self._generate_evpn_cmds(evpn, as_number, vrf)
self._generate_neighbors_cmds(as_number, vrf, bgp_removed)
self._generate_networks_cmds(as_number, vrf, bgp_removed)
def _generate_neighbors_cmds(self, as_number, vrf, bgp_removed):
req_neighbors = self._required_config['neighbors']
curr_neighbors = self._current_config.get('neighbors', {})
evpn = self._required_config.get('evpn')
if self._purge:
for neighbor in curr_neighbors:
remote_as = curr_neighbors[neighbor].get("remote_as")
self._commands.append('router bgp %s vrf %s no neighbor %s remote-as %s' % (
as_number, vrf, neighbor, remote_as))
if req_neighbors is not None:
for neighbor_data in req_neighbors:
neighbor = neighbor_data.get("neighbor")
curr_neighbor = curr_neighbors.get(neighbor)
remote_as = neighbor_data.get("remote_as")
multihop = neighbor_data.get("multihop")
if bgp_removed or curr_neighbor is None:
if remote_as is not None:
self._commands.append(
'router bgp %s vrf %s neighbor %s remote-as %s' % (as_number, vrf, neighbor, remote_as))
if multihop is not None:
self._commands.append(
'router bgp %s vrf %s neighbor %s ebgp-multihop %s' % (as_number, vrf, neighbor, multihop))
if evpn:
self._commands.append(
'router bgp %s vrf %s neighbor %s peer-group evpn' % (as_number, vrf, neighbor))
elif curr_neighbor is not None:
curr_remote_as = curr_neighbor.get("remote_as")
curr_multihop = curr_neighbor.get("multihop")
curr_neighbor_evpn = curr_neighbor.get("evpn")
if remote_as != curr_remote_as:
self._commands.append(
'router bgp %s vrf %s neighbor %s remote-as %s' % (as_number, vrf, neighbor, remote_as))
if multihop is not None and multihop != curr_multihop:
self._commands.append(
'router bgp %s vrf %s neighbor %s ebgp-multihop %s' % (as_number, vrf, neighbor, multihop))
if evpn and curr_neighbor_evpn is not True:
self._commands.append(
'router bgp %s vrf %s neighbor %s peer-group evpn' % (as_number, vrf, neighbor))
def _generate_networks_cmds(self, as_number, vrf, 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 vrf %s network %s /%s' % (
as_number, vrf, 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 _generate_evpn_cmds(self, evpn, as_number, vrf):
if evpn:
for attr in self.EVPN_ENABLE_ATTRS:
curr_attr = self._current_config.get(attr)
if curr_attr is not True:
self._commands.append(self.EVPN_COMMANDS_REGEX_MAPPER.get(attr)[1] % (as_number, vrf))
elif not evpn:
for attr in self.EVPN_DISABLE_ATTRS:
curr_attr = self._current_config.get(attr)
if curr_attr is not False:
self._commands.append("no " + self.EVPN_COMMANDS_REGEX_MAPPER.get(attr)[1] % (as_number, vrf))
def main():
""" main entry point for module execution
"""
OnyxBgpModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,140 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_buffer_pool
author: "Anas Badaha (@anasb)"
short_description: Configures Buffer Pool
description:
- This module provides declarative management of Onyx Buffer Pool configuration
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.8130
options:
name:
description:
- pool name.
required: true
pool_type:
description:
- pool type.
choices: ['lossless', 'lossy']
default: lossy
memory_percent:
description:
- memory percent.
switch_priority:
description:
- switch priority, range 1-7.
'''
EXAMPLES = """
- name: Configure buffer pool
onyx_buffer_pool:
name: roce
pool_type: lossless
memory_percent: 50.00
switch_priority: 3
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- traffic pool roce type lossless
- traffic pool roce memory percent 50.00
- traffic pool roce map switch-priority 3
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxBufferPoolModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
element_spec = dict(
name=dict(type='str', required=True),
pool_type=dict(choices=['lossless', 'lossy'], default='lossy'),
memory_percent=dict(type='float'),
switch_priority=dict(type='int')
)
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(module_params)
self.validate_param_values(self._required_config)
def validate_switch_priority(self, value):
if value and not 0 <= int(value) <= 7:
self._module.fail_json(msg='switch_priority value must be between 0 and 7')
def _set_traffic_pool_config(self, traffic_pool_config):
if traffic_pool_config is None:
return
traffic_pool_config = traffic_pool_config.get(self._required_config.get('name'))
self._current_config['pool_type'] = traffic_pool_config[0].get("Type")
self._current_config['switch_priority'] = int(traffic_pool_config[0].get("Switch Priorities"))
self._current_config['memory_percent'] = float(traffic_pool_config[0].get("Memory [%]"))
def _show_traffic_pool(self):
cmd = "show traffic pool {0}".format(self._required_config.get("name"))
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
traffic_pool_config = self._show_traffic_pool()
self._set_traffic_pool_config(traffic_pool_config)
def generate_commands(self):
name = self._required_config.get("name")
pool_type = self._required_config.get("pool_type")
if self._current_config is None:
self._add_add_traffic_pool_cmds(name, pool_type)
else:
current_pool_type = self._current_config.get("pool_type")
if pool_type != current_pool_type:
self._add_add_traffic_pool_cmds(name, pool_type)
memory_percent = self._required_config.get("memory_percent")
if memory_percent is not None:
curr_memory_percent = self._current_config.get("memory_percent")
if curr_memory_percent is None or memory_percent != curr_memory_percent:
self._commands.append('traffic pool {0} memory percent {1}'.format(name, memory_percent))
switch_priority = self._required_config.get("switch_priority")
if switch_priority is not None:
curr_switch_priority = self._current_config.get("switch_priority")
if curr_switch_priority is None or switch_priority != curr_switch_priority:
self._commands.append('traffic pool {0} map switch-priority {1}'.format(name, switch_priority))
def _add_add_traffic_pool_cmds(self, name, pool_type):
self._commands.append('traffic pool {0} type {1}'.format(name, pool_type))
def main():
""" main entry point for module execution
"""
OnyxBufferPoolModule.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
DOCUMENTATION = '''
---
module: onyx_command
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_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList
from ansible.module_utils.six import string_types
from ansible_collections.mellanox.onyx.plugins.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 been 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,248 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_config
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. If the C(backup_options) value is not given,
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
type: bool
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
type: bool
backup_options:
description:
- This is a dict object containing configurable options related to backup file path.
The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set
to I(no) this option will be silently ignored.
suboptions:
filename:
description:
- The filename to be used to store the backup configuration. If the filename
is not given it will be generated based on the hostname, current time and date
in format defined by <hostname>_config.<current-date>@<current-time>
dir_path:
description:
- This option provides the path ending with directory name in which the backup
configuration file will be stored. If the directory does not exist it will be first
created and the filename is either the value of C(filename) or default filename
as described in C(filename) options description. If the path value is not given
in that case a I(backup) directory will be created in the current working directory
and backup configuration will be copied in C(filename) within I(backup) directory.
type: path
type: dict
'''
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: str
sample: /playbooks/ansible/backup/onyx_config.2016-07-16@22:28:34
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import get_config
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import load_config
from ansible_collections.mellanox.onyx.plugins.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
total_commands = []
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'])
total_commands.extend(commands)
result['updates'] = total_commands
if module.params['save']:
total_commands.append('configuration write')
if total_commands:
result['changed'] = True
if not module.check_mode:
load_config(module, total_commands)
def main():
""" main entry point for module execution
"""
backup_spec = dict(
filename=dict(),
dir_path=dict(type='path')
)
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),
backup_options=dict(type='dict', options=backup_spec),
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,241 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_facts
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 currently 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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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 initialization
"""
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 version', 'show interfaces ethernet']
def populate(self):
super(Interfaces, self).populate()
version_data = self.responses[0]
os_version = version_data['Product release']
data = self.responses[1]
if data:
self.facts['interfaces'] = self.populate_interfaces(data, os_version)
def extractIfData(self, interface_data):
return {"MAC Address": interface_data["Mac address"],
"Actual Speed": interface_data["Actual speed"],
"MTU": interface_data["MTU"],
"Admin State": interface_data["Admin state"],
"Operational State": interface_data["Operational state"]}
def populate_interfaces(self, interfaces, os_version):
interfaces_dict = dict()
for if_data in interfaces:
if_dict = dict()
if os_version >= BaseOnyxModule.ONYX_API_VERSION:
for if_name, interface_data in iteritems(if_data):
interface_data = interface_data[0]
if_dict = self.extractIfData(interface_data)
if_name = if_dict["Interface Name"] = if_name
else:
if_dict = self.extractIfData(if_data)
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,220 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_igmp
author: "Samer Deeb (@samerd)"
short_description: Configures IGMP global parameters
description:
- This module provides declarative management of IGMP protocol params
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.6107
options:
state:
description:
- IGMP state.
required: true
choices: ['enabled', 'disabled']
last_member_query_interval:
description:
- Configure the last member query interval, range 1-25
mrouter_timeout:
description:
- Configure the mrouter timeout, range 60-600
port_purge_timeout:
description:
- Configure the host port purge timeout, range 130-1225
proxy_reporting:
description:
- Configure ip igmp snooping proxy and enable reporting mode
choices: ['enabled', 'disabled']
report_suppression_interval:
description:
- Configure the report suppression interval, range 1-25
unregistered_multicast:
description:
- Configure the unregistered multicast mode
Flood unregistered multicast
Forward unregistered multicast to mrouter ports
choices: ['flood', 'forward-to-mrouter-ports']
default_version:
description:
- Configure the default operating version of the IGMP snooping
choices: ['V2','V3']
'''
EXAMPLES = """
- name: Configure igmp
onyx_igmp:
state: enabled
unregistered_multicast: flood
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- ip igmp snooping
- ip igmp snooping last-member-query-interval 10
- ip igmp snooping mrouter-timeout 150
- ip igmp snooping port-purge-timeout 150
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxIgmpModule(BaseOnyxModule):
TIME_INTERVAL_REGEX = re.compile(r'^(\d+)\s+seconds')
_RANGE_INTERVALS = dict(
last_member_query_interval=(1, 25, 'Last member query interval'),
mrouter_timeout=(60, 600, 'Mrouter timeout'),
port_purge_timeout=(130, 1225, 'Port purge timeout'),
report_suppression_interval=(1, 25, 'Report suppression interval'),
)
def init_module(self):
""" initialize module
"""
element_spec = dict(
state=dict(choices=['enabled', 'disabled'], required=True),
last_member_query_interval=dict(type='int'),
mrouter_timeout=dict(type='int'),
port_purge_timeout=dict(type='int'),
proxy_reporting=dict(choices=['enabled', 'disabled']),
report_suppression_interval=dict(type='int'),
unregistered_multicast=dict(
choices=['flood', 'forward-to-mrouter-ports']),
default_version=dict(choices=['V2', 'V3']),
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def _validate_key(self, param, key):
interval_params = self._RANGE_VALIDATORS.get(key)
if interval_params:
min_val, max_val = interval_params[0], interval_params[1]
value = param.get(key)
self._validate_range(key, min_val, max_val, value)
else:
super(OnyxIgmpModule, self)._validate_key(param, key)
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(module_params)
self.validate_param_values(self._required_config)
def _set_igmp_config(self, igmp_config):
igmp_config = igmp_config[0]
if not igmp_config:
return
self._current_config['state'] = igmp_config.get(
'IGMP snooping globally', 'disabled')
self._current_config['proxy_reporting'] = igmp_config.get(
'Proxy-reporting globally', 'disabled')
self._current_config['default_version'] = igmp_config.get(
'IGMP default version for new VLAN', 'V3')
self._current_config['unregistered_multicast'] = igmp_config.get(
'IGMP snooping unregistered multicast', 'flood')
for interval_name, interval_params in iteritems(self._RANGE_INTERVALS):
display_str = interval_params[2]
value = igmp_config.get(display_str, '')
match = self.TIME_INTERVAL_REGEX.match(value)
if match:
interval_value = int(match.group(1))
else:
interval_value = None
self._current_config[interval_name] = interval_value
def _show_igmp(self):
cmd = "show ip igmp snooping"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
igmp_config = self._show_igmp()
if igmp_config:
self._set_igmp_config(igmp_config)
def generate_commands(self):
state = self._required_config['state']
if state == 'enabled':
self._generate_igmp_cmds()
else:
self._generate_no_igmp_cmds()
def _generate_igmp_cmds(self):
curr_state = self._current_config.get('state', 'disabled')
if curr_state == 'disabled':
self._commands.append('ip igmp snooping')
for interval_name in self._RANGE_INTERVALS:
req_val = self._required_config.get(interval_name)
if not req_val:
continue
curr_value = self._current_config.get(interval_name)
if curr_value == req_val:
continue
interval_cmd = interval_name.replace('_', '-')
self._commands.append(
'ip igmp snooping %s %s' % (interval_cmd, req_val))
req_val = self._required_config.get('unregistered_multicast')
if req_val:
curr_value = self._current_config.get(
'unregistered_multicast', 'flood')
if req_val != curr_value:
self._commands.append(
'ip igmp snooping unregistered multicast %s' % req_val)
req_val = self._required_config.get('proxy_reporting')
if req_val:
curr_value = self._current_config.get(
'proxy_reporting', 'disabled')
if req_val != curr_value:
cmd = 'ip igmp snooping proxy reporting'
if req_val == 'disabled':
cmd = 'no %s' % cmd
self._commands.append(cmd)
req_val = self._required_config.get('default_version')
if req_val:
curr_value = self._current_config.get(
'default_version', 'V3')
if req_val != curr_value:
version = req_val[1] # remove the 'V' and take the number only
self._commands.append(
'ip igmp snooping version %s' % version)
def _generate_no_igmp_cmds(self):
curr_state = self._current_config.get('state', 'disabled')
if curr_state != 'disabled':
self._commands.append('no ip igmp snooping')
def main():
""" main entry point for module execution
"""
OnyxIgmpModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,131 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_igmp_interface
author: "Anas Badaha (@anasb)"
short_description: Configures IGMP interface parameters
description:
- This module provides declarative management of IGMP interface configuration
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.8130
options:
name:
description:
- interface name that we want to configure IGMP on it
required: true
state:
description:
- IGMP Interface state.
choices: ['enabled', 'disabled']
default: enabled
'''
EXAMPLES = """
- name: Configure igmp interface
onyx_igmp_interface:
state: enabled
name: Eth1/1
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/1 ip igmp snooping fast-leave
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxIgmpInterfaceModule(BaseOnyxModule):
IF_NAME_REGEX = re.compile(r"^(Eth\d+\/\d+|Eth\d+\/\d+\d+)$")
def init_module(self):
""" initialize module
"""
element_spec = dict(
state=dict(choices=['enabled', 'disabled'], default='enabled'),
name=dict(required=True)
)
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(module_params)
match = self.IF_NAME_REGEX.match(self._required_config["name"])
if not match:
raise AttributeError("Please Insert Valid Interface Name")
self.validate_param_values(self._required_config)
def _set_igmp_config(self, igmp_interfaces_config):
if not igmp_interfaces_config:
return
name = self._required_config.get('name')
interface_state = igmp_interfaces_config[name][0].get('leave-mode')
if interface_state == "Fast":
self._current_config['state'] = "enabled"
else:
self._current_config['state'] = "disabled"
def _show_igmp_interfaces(self):
cmd = "show ip igmp snooping interfaces"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
igmp_interfaces_config = self._show_igmp_interfaces()
if igmp_interfaces_config:
self._set_igmp_config(igmp_interfaces_config)
def generate_commands(self):
req_state = self._required_config['state']
self._req_val = self._required_config.get('name').replace("Eth", "ethernet ")
if req_state == 'enabled':
self._generate_igmp_interface_cmds()
else:
self._generate_no_igmp_cmds()
def _generate_igmp_interface_cmds(self):
curr_state = self._current_config.get('state', 'enabled')
if curr_state == 'enabled':
pass
elif curr_state == 'disabled':
self._commands.append('interface %s ip igmp snooping fast-leave' % self._req_val)
def _generate_no_igmp_cmds(self):
curr_state = self._current_config.get('state', 'enabled')
if curr_state == 'enabled':
self._commands.append('interface %s no ip igmp snooping fast-leave' % self._req_val)
else:
pass
def main():
""" main entry point for module execution
"""
OnyxIgmpInterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,431 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_igmp_vlan
author: Anas Badaha (@anasbadaha)
short_description: Configures IGMP Vlan parameters
description:
- This module provides declarative management of IGMP vlan configuration on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.7.0932-01
options:
vlan_id:
description:
- VLAN ID, vlan should exist.
required: true
state:
description:
- IGMP state.
choices: ['enabled', 'disabled']
default: enabled
mrouter:
description:
- Configure ip igmp snooping mrouter port on vlan
suboptions:
state:
description:
- Enable IGMP snooping mrouter on vlan interface.
choices: ['enabled', 'disabled']
default: enabled
name:
description:
- Configure mrouter interface
required: true
querier:
description:
- Configure the IGMP querier parameters
suboptions:
state:
description:
- Enable IGMP snooping querier on vlan in the switch.
choices: ['enabled', 'disabled']
default: enabled
interval:
description:
- Update time interval between querier queries, range 60-600
address:
description:
- Update IP address for the querier
static_groups:
description:
- List of IGMP static groups.
suboptions:
multicast_ip_address:
description:
- Configure static IP multicast group, range 224.0.1.0-239.255.255.25.
required: true
name:
description:
- interface name to configure static groups on it.
sources:
description:
- List of IP sources to be configured
version:
description:
- IGMP snooping operation version on this vlan
choices: ['V2','V3']
'''
EXAMPLES = """
- name: Configure igmp vlan
onyx_igmp_vlan:
state: enabled
vlan_id: 10
version:
V2
querier:
state: enabled
interval: 70
address: 10.11.121.13
mrouter:
state: disabled
name: Eth1/2
static_groups:
- multicast_ip_address: 224.5.5.8
name: Eth1/1
sources:
- 1.1.1.1
- 1.1.1.2
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- vlan 10 ip igmp snooping
- vlan 10 ip igmp snooping static-group 224.5.5.5 interface ethernet 1/1
"""
import socket
import struct
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
def _ip_to_int(addr):
return struct.unpack("!I", socket.inet_aton(addr))[0]
class OnyxIgmpVlanModule(BaseOnyxModule):
MIN_MULTICAST_IP = _ip_to_int("224.0.1.0")
MAX_MULTICAST_IP = _ip_to_int("239.255.255.255")
def init_module(self):
""" initialize module
"""
mrouter_spec = dict(name=dict(required=True),
state=dict(choices=['enabled', 'disabled'], default='enabled'))
querier_spec = dict(state=dict(choices=['enabled', 'disabled'], default='enabled'),
interval=dict(type='int'), address=dict())
static_groups_spec = dict(multicast_ip_address=dict(required=True),
name=dict(required=True), sources=dict(type='list'))
element_spec = dict(vlan_id=dict(type='int', required=True),
state=dict(choices=['enabled', 'disabled'], default='enabled'),
querier=dict(type='dict', options=querier_spec),
static_groups=dict(type='list', elements='dict', options=static_groups_spec),
mrouter=dict(type='dict', options=mrouter_spec),
version=dict(choices=['V2', 'V3']))
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(module_params)
self.validate_param_values(self._required_config)
def _validate_attr_is_not_none(self, attr_name, attr_value):
if attr_name == 'vlan_id' or attr_name == 'state':
pass
elif attr_value is not None:
self._module.fail_json(msg='Can not set %s value on switch while state is disabled' % attr_name)
def validate_param_values(self, obj, param=None):
if obj['state'] == 'disabled':
for attr_name in obj:
self._validate_attr_is_not_none(attr_name, obj[attr_name])
super(OnyxIgmpVlanModule, self).validate_param_values(obj, param)
def validate_querier(self, value):
interval = value.get('interval')
if interval and not 60 <= int(interval) <= 600:
self._module.fail_json(msg='query-interval value must be between 60 and 600')
def validate_static_groups(self, value):
multicast_ip = value.get('multicast_ip_address')
multicast_ip = _ip_to_int(multicast_ip)
if multicast_ip < self.MIN_MULTICAST_IP or multicast_ip > self.MAX_MULTICAST_IP:
self._module.fail_json(msg='multicast IP address must be in range 224.0.1.0 - 239.255.255.255')
@staticmethod
def _get_curr_mrouter_config(mrouter_port):
if mrouter_port == "none":
return {'state': 'disabled'}
else:
return {'state': 'enabled',
'name': mrouter_port}
def _get_curr_querier_config(self, querier_config):
if "Non-Querier" in querier_config:
return {'state': 'disabled'}
elif "Querier" in querier_config:
igmp_querier_config = self._show_igmp_querier_config()[0]
snooping_querier_info = igmp_querier_config["Snooping querier information for VLAN %d" % (
self._required_config['vlan_id'])]
snooping_querier_info = snooping_querier_info[1]
interval = int(snooping_querier_info["Query interval"])
address = snooping_querier_info["Configured querier IP address"]
return {'state': 'enabled',
'interval': interval,
'address': address}
@staticmethod
def _get_curr_version(version):
if "V3" in version:
return "V3"
elif "V2" in version:
return "V2"
def _get_curr_static_group_config(self, multicast_ip_address):
sources = None
names = None
igmp_snooping_groups_config = self._show_igmp_snooping_groups_config(multicast_ip_address)
if igmp_snooping_groups_config is not None:
igmp_snooping_groups_config = igmp_snooping_groups_config[0]
snooping_group_information = igmp_snooping_groups_config.get('Snooping group '
'information for VLAN %d and group '
'%s' % (self._required_config['vlan_id'],
multicast_ip_address))
if snooping_group_information is not None:
if len(snooping_group_information) == 1:
names = snooping_group_information[0].get('V1/V2 Receiver Ports')
elif len(snooping_group_information) == 2:
sources_dict = dict()
v3_receiver_ports = snooping_group_information[1].get('V3 Receiver Ports')
ports_number = v3_receiver_ports[0].get('Port Number')
sources = v3_receiver_ports[0].get('Include sources')
if isinstance(ports_number, list):
i = 0
for port_number in ports_number:
sources_dict[port_number] = sources[i]
i += 1
else:
sources_dict[ports_number] = sources
names = snooping_group_information[0].get('V1/V2 Receiver Ports')
sources = sources_dict
return {'sources': sources,
'names': names}
else:
return None
else:
return None
def _set_igmp_config(self, igmp_vlan_config):
igmp_vlan_config = igmp_vlan_config[0]
if not igmp_vlan_config:
return
self._current_config['state'] = igmp_vlan_config.get('message 1')
if "enabled" in self._current_config['state']:
self._current_config['state'] = "enabled"
elif "disabled" in self._current_config['state']:
self._current_config['state'] = "disabled"
mrouter_port = igmp_vlan_config.get('mrouter static port list')
self._current_config['mrouter'] = dict(self._get_curr_mrouter_config(mrouter_port))
querier_config = igmp_vlan_config.get('message 3')
self._current_config['querier'] = dict(self._get_curr_querier_config(querier_config))
version = igmp_vlan_config.get('message 2')
self._current_config['version'] = self._get_curr_version(version)
req_static_groups = self._required_config.get('static_groups')
if req_static_groups is not None:
static_groups = self._current_config['static_groups'] = dict()
for static_group in req_static_groups:
static_group_config = self._get_curr_static_group_config(static_group['multicast_ip_address'])
static_groups[static_group['multicast_ip_address']] = static_group_config
def _show_igmp_vlan(self):
cmd = ("show ip igmp snooping vlan %d" % self._required_config['vlan_id'])
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _show_igmp_querier_config(self):
cmd = ("show ip igmp snooping querier vlan %d " % self._required_config['vlan_id'])
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _show_igmp_snooping_groups_config(self, multicast_ip_address):
cmd = ("show ip igmp snooping groups vlan %d group %s" % (self._required_config['vlan_id'],
multicast_ip_address))
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
igmp_vlan_config = self._show_igmp_vlan()
if igmp_vlan_config:
self._set_igmp_config(igmp_vlan_config)
def generate_commands(self):
req_state = self._required_config.get('state', 'enabled')
self._generate_igmp_vlan_cmds(req_state)
_mrouter = self._required_config.get('mrouter')
if _mrouter is not None:
self._generate_igmp_mrouter_cmds(_mrouter)
_querier = self._required_config.get('querier')
if _querier is not None:
req_querier_state = _querier.get('state', 'enabled')
self._generate_igmp_querier_cmds(req_querier_state)
req_querier_interval = _querier.get('interval')
if req_querier_interval is not None:
self._gen_querier_attr_commands("interval", req_querier_interval, "query-interval")
req_querier_address = _querier.get('address')
if req_querier_address is not None:
self._gen_querier_attr_commands("address", req_querier_address, "address")
_version = self._required_config.get('version')
if _version is not None:
self._generate_igmp_version_cmds(_version)
_static_groups = self._required_config.get('static_groups')
if _static_groups is not None:
for static_group in _static_groups:
self._generate_igmp_static_groups_cmd(static_group)
def _add_igmp_vlan_commands(self, req_state):
if req_state == 'enabled':
igmp_vlan_cmd = 'vlan %d ip igmp snooping' % self._required_config['vlan_id']
else:
igmp_vlan_cmd = 'vlan %d no ip igmp snooping' % self._required_config['vlan_id']
self._commands.append(igmp_vlan_cmd)
def _generate_igmp_vlan_cmds(self, req_state):
curr_state = self._current_config.get('state')
if curr_state != req_state:
self._add_igmp_vlan_commands(req_state)
def _gen_querier_attr_commands(self, attr_name, req_attr_value, attr_cmd_name):
_curr_querier = self._current_config.get('querier')
curr_querier_val = _curr_querier.get(attr_name)
if req_attr_value != curr_querier_val:
self._commands.append('vlan %d ip igmp snooping querier %s %s' % (self._required_config['vlan_id'],
attr_cmd_name, req_attr_value))
def _add_querier_commands(self, req_querier_state):
if req_querier_state == 'enabled':
self._commands.append('vlan %d ip igmp snooping querier' % self._required_config['vlan_id'])
elif req_querier_state == 'disabled':
self._commands.append('vlan %d no ip igmp snooping querier' % (
self._required_config['vlan_id']))
def _generate_igmp_querier_cmds(self, req_querier_state):
_curr_querier = self._current_config.get('querier')
curr_querier_state = _curr_querier.get('state')
if req_querier_state != curr_querier_state:
self._add_querier_commands(req_querier_state)
def _generate_igmp_version_cmds(self, version):
_curr_version = self._current_config.get('version')
if version != _curr_version:
self._commands.append('vlan %d ip igmp snooping version %s' % (
self._required_config['vlan_id'], version[1]))
def _add_mrouter_commands(self, req_mrouter, curr_mrouter):
curr_state = curr_mrouter.get('state')
curr_interface = curr_mrouter.get('name')
req_state = req_mrouter.get('state')
req_interface = req_mrouter.get('name')
mrouter_interface = req_interface.replace("Eth", "ethernet ")
if curr_state == 'enabled' and req_state == 'disabled':
self._commands.append('vlan %d no ip igmp snooping mrouter interface '
'%s' % (self._required_config['vlan_id'], mrouter_interface))
elif curr_state == 'disabled' and req_state == 'enabled':
self._commands.append('vlan %d ip igmp snooping mrouter interface '
'%s' % (self._required_config['vlan_id'], mrouter_interface))
elif req_state == 'enabled' and curr_state == 'enabled' and req_interface != curr_interface:
self._commands.append('vlan %d ip igmp snooping mrouter interface '
'%s' % (self._required_config['vlan_id'], mrouter_interface))
def _generate_igmp_mrouter_cmds(self, req_mrouter):
curr_mrouter = self._current_config.get('mrouter')
if curr_mrouter != req_mrouter:
self._add_mrouter_commands(req_mrouter, curr_mrouter)
def _add_igmp_static_groups_cmd(self, req_name, req_multicast_ip_address, curr_names):
if curr_names is None:
self._commands.append('vlan %d ip igmp snooping static-group %s interface %s' % (
self._required_config['vlan_id'], req_multicast_ip_address, req_name.replace('Eth', 'ethernet ')))
elif req_name.replace('E', 'e') not in curr_names:
self._commands.append('vlan %d ip igmp snooping static-group %s interface %s' % (
self._required_config['vlan_id'], req_multicast_ip_address, req_name.replace('Eth', 'ethernet ')))
def _add_igmp_static_groups_sources_cmd(self, req_sources, req_name, req_multicast_ip_address, curr_sources):
if curr_sources is None:
for source in req_sources:
self._commands.append('vlan %d ip igmp snooping static-group %s interface %s source %s' % (
self._required_config['vlan_id'], req_multicast_ip_address, req_name.replace('Eth', 'ethernet '),
source))
else:
curr_sources = curr_sources.get(req_name.replace('E', 'e'))
if curr_sources is None:
curr_sources = set([])
else:
curr_sources = set(x.strip() for x in curr_sources.split(','))
sources_to_add = set(req_sources) - set(curr_sources)
sources_to_remove = set(curr_sources) - set(req_sources)
if len(sources_to_add) != 0:
for source in sources_to_add:
self._commands.append('vlan %d ip igmp snooping static-group %s interface %s source %s' % (
self._required_config['vlan_id'], req_multicast_ip_address,
req_name.replace('Eth', 'ethernet '), source))
if len(sources_to_remove) != 0:
for source in sources_to_remove:
self._commands.append('vlan %d no ip igmp snooping static-group %s interface %s source %s' % (
self._required_config['vlan_id'], req_multicast_ip_address,
req_name.replace('Eth', 'ethernet '),
source))
def _generate_igmp_static_groups_cmd(self, static_group):
req_multicast_ip_address = static_group.get('multicast_ip_address')
req_name = static_group.get('name')
req_sources = static_group.get('sources')
curr_static_groups = self._current_config.get('static_groups')
curr_static_group = curr_static_groups.get(req_multicast_ip_address)
curr_names = None
curr_sources = None
if curr_static_group is not None:
curr_names = curr_static_group.get('names')
curr_sources = curr_static_group.get('sources')
self._add_igmp_static_groups_cmd(req_name, req_multicast_ip_address, curr_names)
if req_sources is not None:
self._add_igmp_static_groups_sources_cmd(req_sources, req_name, req_multicast_ip_address, curr_sources)
def main():
""" main entry point for module execution
"""
OnyxIgmpVlanModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,497 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_interface
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).
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
rx_rate:
description:
- Receiver rate in bits per second (bps).
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
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: 100G
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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import get_interfaces_config
class OnyxInterfaceModule(BaseOnyxModule):
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|\d+\/\d+\/\d+)$")
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
IF_LOOPBACK_REGEX = re.compile(r"^Loopback (\d+)$")
IF_PO_REGEX = re.compile(r"^Po(\d+)$")
IF_TYPE_ETH = "ethernet"
IF_TYPE_LOOPBACK = "loopback"
IF_TYPE_VLAN = "vlan"
IF_TYPE_PO = "port-channel"
IF_TYPE_MAP = {
IF_TYPE_ETH: IF_ETH_REGEX,
IF_TYPE_VLAN: IF_VLAN_REGEX,
IF_TYPE_LOOPBACK: IF_LOOPBACK_REGEX,
IF_TYPE_PO: IF_PO_REGEX
}
UNSUPPORTED_ATTRS = {
IF_TYPE_ETH: (),
IF_TYPE_VLAN: ('speed', 'rx_rate', 'tx_rate'),
IF_TYPE_LOOPBACK: ('speed', 'mtu', 'rx_rate', 'tx_rate'),
IF_TYPE_PO: ('speed', 'rx_rate', 'tx_rate'),
}
UNSUPPORTED_STATES = {
IF_TYPE_ETH: ('absent',),
IF_TYPE_VLAN: (),
IF_TYPE_LOOPBACK: ('up', 'down'),
IF_TYPE_PO: ('absent'),
}
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 aggregate 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._os_version = self._get_os_version()
self._current_config = dict()
config = self._get_interfaces_config()
if not config:
return
if self._os_version < self.ONYX_API_VERSION:
for if_data in config:
if_name = self.get_if_name(if_data)
self._current_config[if_name] = self._create_if_data(
if_name, if_data)
else:
if_data = dict()
for if_config in config:
for if_name, if_attr in iteritems(if_config):
for config in if_attr:
for key, value in iteritems(config):
if_data[key] = value
self._current_config[if_name] = self._create_if_data(
if_name, if_data)
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,294 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_l2_interface
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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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')
interface_allwoed_vlans = []
if allowed_vlans:
vlans = [x.strip() for x in allowed_vlans.split(',')]
for vlan in vlans:
if '-' not in vlan:
interface_allwoed_vlans.append(int(vlan))
else:
vlan_range = vlan.split("-")
min_number = int(vlan_range[0].strip())
max_number = int(vlan_range[1].strip())
vlan_list = range(min_number, max_number + 1)
interface_allwoed_vlans.extend(vlan_list)
return interface_allwoed_vlans
@classmethod
def get_access_vlan(cls, if_data):
access_vlan = cls.get_config_attr(if_data, 'Access vlan')
if access_vlan:
try:
return int(access_vlan)
except ValueError:
return None
def _create_switchport_data(self, if_name, if_data):
if self._os_version >= self.ONYX_API_VERSION:
if_data = if_data[0]
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._os_version = self._get_os_version()
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:
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))
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))
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,297 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_l3_interface
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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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):
if self._os_version < self.ONYX_API_VERSION:
for if_data in if_config:
if_name = self.get_config_attr(if_data, 'header')
self._get_if_attributes(if_type, if_name, if_data)
else:
for if_config_item in if_config:
for if_name, if_data in iteritems(if_config_item):
if_data = if_data[0]
self._get_if_attributes(if_type, if_name, if_data)
def _get_if_attributes(self, if_type, if_name, if_data):
ipaddr_attr = self.IP_ADDR_ATTR_MAP[if_type]
regex = self.IF_TYPE_MAP[if_type]
match = regex.match(if_name)
if not match:
return
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._os_version = self._get_os_version()
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,349 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_linkagg
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_collections.ansible.netcommon.plugins.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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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'
MLAG_SUMMARY = 'MLAG Port-Channel Summary'
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:
if self._os_version >= self.ONYX_API_VERSION:
found_summary = False
for summary_item in lag_summary:
if self.MLAG_SUMMARY in summary_item:
lag_summary = summary_item[self.MLAG_SUMMARY]
if lag_summary:
lag_summary = lag_summary[0]
else:
lag_summary = dict()
found_summary = True
break
if not found_summary:
lag_summary = dict()
else:
lag_summary = lag_summary.get(self.MLAG_SUMMARY, dict())
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()
self._os_version = self._get_os_version()
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)
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,112 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_lldp
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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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,224 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_lldp_interface
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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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,231 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_magp
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.six import iteritems
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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):
if self._os_version >= self.ONYX_API_VERSION:
for magp_config in magp_data:
for magp_name, data in iteritems(magp_config):
magp_id = int(magp_name.replace('MAGP ', ''))
self._current_config[magp_id] = \
self._create_magp_instance_data(magp_id, data[0])
else:
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._os_version = self._get_os_version()
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,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
DOCUMENTATION = '''
---
module: onyx_mlag_ipl
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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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,180 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_mlag_vip
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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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_ip is not None:
if req_group is None:
self._module.fail_json(msg='In order to configure Mlag-Vip you must send '
'group name param beside IPaddress')
ipaddr, mask = req_ip.split('/')
if req_group != current_group or req_ip != current_ip:
self._commands.append('mlag-vip %s ip %s /%s force' % (req_group, ipaddr, mask))
elif req_group and req_group != current_group:
self._commands.append('mlag-vip %s' % req_group)
if req_mac and 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,239 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_ntp
version_added: '0.2.0'
author: "Sara-Touqan (@sarato)"
short_description: Manage NTP general configurations and ntp keys configurations on Mellanox ONYX network devices
description:
- This module provides declarative management of NTP & NTP Keys
on Mellanox ONYX network devices.
options:
state:
description:
- State of the NTP configuration.
choices: ['enabled', 'disabled']
type: str
authenticate_state:
description:
- State of the NTP authentication configuration.
choices: ['enabled', 'disabled']
type: str
ntp_authentication_keys:
type: list
description:
- List of ntp authentication keys
suboptions:
auth_key_id:
description:
- Configures ntp key-id, range 1-65534
required: true
type: int
auth_key_encrypt_type:
description:
- encryption type used to configure ntp authentication key.
required: true
choices: ['md5', 'sha1']
type: str
auth_key_password:
description:
- password used for ntp authentication key.
required: true
type: str
auth_key_state:
description:
- Used to decide if you want to delete given ntp key or not
choices: ['present', 'absent']
type: str
trusted_keys:
type: list
description:
- List of ntp trusted keys
'''
EXAMPLES = """
- name: Configure NTP
onyx_ntp:
state: enabled
authenticate_state: enabled
ntp_authentication_keys:
- auth_key_id: 1
auth_key_encrypt_type: md5
auth_key_password: 12345
auth_key_state: absent
trusted_keys: 1,2,3
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- ntp enable
- ntp disable
- ntp authenticate
- no ntp authenticate
- ntp authentication-key 1 md5 12345
- no ntp authentication-key 1
- ntp trusted-key 1,2,3
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
class OnyxNTPModule(BaseOnyxModule):
def init_module(self):
""" module initialization
"""
ntp_authentication_key_spec = dict(auth_key_id=dict(type='int', required=True),
auth_key_encrypt_type=dict(required=True, choices=['md5', 'sha1']),
auth_key_password=dict(required=True),
auth_key_state=dict(choices=['present', 'absent']))
element_spec = dict(
state=dict(choices=['enabled', 'disabled']),
authenticate_state=dict(choices=['enabled', 'disabled']),
ntp_authentication_keys=dict(type='list', elements='dict', options=ntp_authentication_key_spec),
trusted_keys=dict(type='list', elements='int')
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def _validate_key_id(self):
keys_id_list = self._required_config.get("ntp_authentication_keys")
if keys_id_list:
for key_item in keys_id_list:
key_id = key_item.get("auth_key_id")
if (key_id < 1) or (key_id > 65534):
self._module.fail_json(
msg='Invalid Key value, value should be in the range 1-65534')
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(module_params)
self.validate_param_values(self._required_config)
self._validate_key_id()
def _show_ntp_config(self):
show_cmds = []
cmd = "show ntp"
show_cmds.append(show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False))
cmd = "show ntp keys"
show_cmds.append(show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False))
return show_cmds
def _set_ntp_keys_config(self, ntp_config):
if not ntp_config:
return
for req_ntp_auth_key in ntp_config:
ecryption_type = req_ntp_auth_key.get("Encryption Type")
self._current_config[req_ntp_auth_key.get("header")] = ecryption_type
def _set_ntp_config(self, ntp_config):
ntp_config = ntp_config[0]
if not ntp_config:
return
self._current_config['state'] = ntp_config.get("NTP is administratively")
self._current_config['authenticate_state'] = ntp_config.get("NTP Authentication administratively")
def load_current_config(self):
self._current_config = dict()
ntp_config = self._show_ntp_config()
if ntp_config:
if ntp_config[0]:
self._set_ntp_config(ntp_config[0])
if ntp_config[1]:
self._set_ntp_keys_config(ntp_config[1])
def generate_commands(self):
current_state = self._current_config.get("state")
state = self._required_config.get("state")
if state is None:
state = current_state
if state is not None:
if current_state != state:
if state == 'enabled':
self._commands.append('ntp enable')
else:
self._commands.append('no ntp enable')
authenticate_state = self._required_config.get("authenticate_state")
if authenticate_state:
current_authenticate_state = self._current_config.get("authenticate_state")
if authenticate_state is not None:
if current_authenticate_state != authenticate_state:
if authenticate_state == 'enabled':
self._commands.append('ntp authenticate')
else:
self._commands.append('no ntp authenticate')
req_ntp_auth_keys = self._required_config.get('ntp_authentication_keys')
if req_ntp_auth_keys:
if req_ntp_auth_keys is not None:
for req_ntp_auth_key in req_ntp_auth_keys:
req_key_id = req_ntp_auth_key.get('auth_key_id')
req_key = 'NTP Key ' + str(req_key_id)
current_req_key = self._current_config.get(req_key)
auth_key_state = req_ntp_auth_key.get('auth_key_state')
req_encrypt_type = req_ntp_auth_key.get('auth_key_encrypt_type')
req_password = req_ntp_auth_key.get('auth_key_password')
if current_req_key:
if req_encrypt_type == current_req_key:
if auth_key_state:
if auth_key_state == 'absent':
self._commands.append('no ntp authentication-key {0}' .format(req_key_id))
else:
continue
else:
if auth_key_state:
if auth_key_state == 'present':
self._commands.append('ntp authentication-key {0} {1} {2}'
.format(req_key_id,
req_encrypt_type,
req_password))
else:
self._commands.append('ntp authentication-key {0} {1} {2}'
.format(req_key_id,
req_encrypt_type,
req_password))
else:
if auth_key_state:
if auth_key_state == 'present':
self._commands.append('ntp authentication-key {0} {1} {2}'
.format(req_key_id,
req_encrypt_type,
req_password))
else:
self._commands.append('ntp authentication-key {0} {1} {2}'
.format(req_key_id,
req_encrypt_type,
req_password))
req_trusted_keys = self._required_config.get('trusted_keys')
if req_trusted_keys:
for key in req_trusted_keys:
self._commands.append('ntp trusted-key {0}' .format(key))
def main():
""" main entry point for module execution
"""
OnyxNTPModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,282 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_ntp_servers_peers
version_added: '0.2.0'
author: "Sara-Touqan (@sarato)"
short_description: Configures NTP peers and servers parameters
description:
- This module provides declarative management of NTP peers and servers configuration on Mellanox ONYX network devices.
options:
peer:
type: list
description:
- List of ntp peers.
suboptions:
ip_or_name:
description:
- Configures ntp peer name or ip.
required: true
type: str
enabled:
description:
- Disables/Enables ntp peer state
type: bool
version:
description:
- version number for the ntp peer
choices: [3, 4]
type: int
key_id:
description:
- Used to configure the key-id for the ntp peer
type: int
state:
description:
- Indicates if the ntp peer exists or should be deleted
choices: ['present', 'absent']
type: str
server:
type: list
description:
- List of ntp servers.
suboptions:
ip_or_name:
description:
- Configures ntp server name or ip.
required: true
type: str
enabled:
description:
- Disables/Enables ntp server
type: bool
trusted_enable:
description:
- Disables/Enables the trusted state for the ntp server.
type: bool
version:
description:
- version number for the ntp server
choices: [3, 4]
type: int
key_id:
description:
- Used to configure the key-id for the ntp server
type: int
state:
description:
- Indicates if the ntp peer exists or should be deleted.
choices: ['present', 'absent']
type: str
ntpdate:
description:
- Sets system clock once from a remote server using NTP.
type: str
'''
EXAMPLES = """
- name: Configure NTP peers and servers
onyx_ntp_peers_servers:
peer:
- ip_or_name: 1.1.1.1
enabled: yes
version: 4
key_id: 6
state: present
server:
- ip_or_name: 2.2.2.2
enabled: true
version: 3
key_id: 8
trusted_enable: no
state: present
ntpdate: 192.168.10.10
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- ntp peer 1.1.1.1 disable
no ntp peer 1.1.1.1 disable
ntp peer 1.1.1.1 keyId 6
ntp peer 1.1.1.1 version 4
no ntp peer 1.1.1.1
ntp server 2.2.2.2 disable
no ntp server 2.2.2.2 disable
ntp server 2.2.2.2 keyID 8
ntp server 2.2.2.2 version 3
ntp server 2.2.2.2 trusted-enable
no ntp server 2.2.2.2
ntp server 192.168.10.10
ntpdate 192.168.10.10
"""
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
class OnyxNTPServersPeersModule(BaseOnyxModule):
def init_module(self):
""" module initialization
"""
peer_spec = dict(ip_or_name=dict(required=True),
enabled=dict(type='bool'),
version=dict(type='int', choices=[3, 4]),
key_id=dict(type='int'),
state=dict(choices=['present', 'absent']))
server_spec = dict(ip_or_name=dict(required=True),
enabled=dict(type='bool'),
version=dict(type='int', choices=[3, 4]),
trusted_enable=dict(type='bool'),
key_id=dict(type='int'),
state=dict(choices=['present', 'absent']))
element_spec = dict(peer=dict(type='list', elements='dict', options=peer_spec),
server=dict(type='list', elements='dict', options=server_spec),
ntpdate=dict())
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(module_params)
self.validate_param_values(self._required_config)
def _show_peers_servers_config(self):
cmd = "show ntp configured"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _set_servers_config(self, peers_servers_config):
servers = dict()
peers = dict()
if not peers_servers_config:
return
index = 0
for peer_server in peers_servers_config:
if (index == 0):
index += 1
continue
else:
header_list = peer_server.get("header").split(" ")
header_type = header_list[1]
if peer_server.get("Enabled") == "yes":
enabled_state = True
else:
enabled_state = False
if (header_type == 'server'):
trusted_state = peer_server.get("Trusted")
if trusted_state == 'yes':
trusted_state = True
else:
trusted_state = False
server_entry = {"version": peer_server.get("NTP version"),
"enabled": enabled_state,
"trusted_enable": trusted_state,
"key_id": peer_server.get("Key ID")}
servers[header_list[2]] = server_entry
else:
peer_entry = {"version": peer_server.get("NTP version"),
"enabled": enabled_state,
"key_id": peer_server.get("Key ID")}
peers[header_list[2]] = peer_entry
index += 1
self._current_config = dict(server=servers,
peer=peers)
def load_current_config(self):
servers = dict()
peers = dict()
self._current_config = dict(server=servers,
peer=peers)
peers_servers_config = self._show_peers_servers_config()
if peers_servers_config:
self._set_servers_config(peers_servers_config)
def generate_commands(self):
for option in self._current_config:
req_ntp = self._required_config.get(option)
if req_ntp is not None:
for ntp_peer in req_ntp:
peer_name = ntp_peer.get('ip_or_name')
peer_key = ntp_peer.get('key_id')
peer_state = ntp_peer.get("state")
peer_enabled = ntp_peer.get("enabled")
peer_version = ntp_peer.get("version")
peer_key = ntp_peer.get("key_id")
curr_name = self._current_config.get(option).get(peer_name)
peer_version = ntp_peer.get('version')
if self._current_config.get(option) and curr_name:
if peer_state:
if(peer_state == "absent"):
self._commands.append('no ntp {0} {1}' .format(option, peer_name))
continue
if peer_enabled is not None:
if curr_name.get("enabled") != peer_enabled:
if(peer_enabled is True):
self._commands.append('no ntp {0} {1} disable' .format(option, peer_name))
else:
self._commands.append('ntp {0} {1} disable' .format(option, peer_name))
if peer_version:
if (int(curr_name.get("version")) != peer_version):
self._commands.append('ntp {0} {1} version {2}' .format(option, peer_name, peer_version))
if peer_key:
if curr_name.get("key_id") != "none":
if (int(curr_name.get("key_id")) != peer_key):
self._commands.append('ntp {0} {1} keyID {2}' .format(option, peer_name, peer_key))
else:
self._commands.append('ntp {0} {1} keyID {2}' .format(option, peer_name, peer_key))
if option == "server":
server_trusted = ntp_peer.get("trusted_enable")
if server_trusted is not None:
if (curr_name.get("trusted_enable") != server_trusted):
if server_trusted is True:
self._commands.append('ntp {0} {1} trusted-enable' .format(option, peer_name))
else:
self._commands.append('no ntp {0} {1} trusted-enable' .format(option, peer_name))
else:
if peer_state:
if(peer_state == "absent"):
continue
if peer_enabled is not None:
if(peer_enabled is True):
self._commands.append('no ntp {0} {1} disable' .format(option, peer_name))
else:
self._commands.append('ntp {0} {1} disable' .format(option, peer_name))
else:
self._commands.append('ntp {0} {1} disable' .format(option, peer_name))
if peer_version:
self._commands.append('ntp {0} {1} version {2}' .format(option, peer_name, peer_version))
if peer_key:
self._commands.append('ntp {0} {1} keyID {2}' .format(option, peer_name, peer_key))
ntpdate = self._required_config.get("ntpdate")
if ntpdate is not None:
self._commands.append('ntpdate {0}' .format(ntpdate))
def main():
""" main entry point for module execution
"""
OnyxNTPServersPeersModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,233 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_ospf
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:
- Interface 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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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,208 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_pfc_interface
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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.six import iteritems
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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._os_version = self._get_os_version()
self._current_config = dict()
pfc_config = self._get_pfc_config()
if not pfc_config:
return
if self._os_version >= self.ONYX_API_VERSION:
if len(pfc_config) >= 3:
pfc_config = pfc_config[2]
else:
pfc_config = dict()
else:
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,191 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_protocol
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']
nve:
description: nve protocol
choices: ['enabled', 'disabled']
bfd:
description: bfd protocol
choices: ['enabled', 'disabled']
version_added: '0.2.0'
'''
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_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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"),
nve=dict(name="nve", enable="protocol nve",
disable="no protocol nve"),
bfd=dict(name="bfd", enable="protocol bfd",
disable="no protocol bfd"),
)
@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,202 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_ptp_global
author: "Anas Badaha (@anasb)"
short_description: Configures PTP Global parameters
description:
- This module provides declarative management of PTP Global configuration
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.8130
ptp and ntp protocols cannot be enabled at the same time
options:
ptp_state:
description:
- PTP state.
choices: ['enabled', 'disabled']
default: enabled
ntp_state:
description:
- NTP state.
choices: ['enabled', 'disabled']
domain:
description:
- "set PTP domain number Range 0-127"
primary_priority:
description:
- "set PTP primary priority Range 0-225"
secondary_priority:
description:
- "set PTP secondary priority Range 0-225"
'''
EXAMPLES = """
- name: Configure PTP
onyx_ptp_global:
ntp_state: enabled
ptp_state: disabled
domain: 127
primary_priority: 128
secondary_priority: 128
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- no ntp enable
- protocol ptp
- ptp domain 127
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxPtpGlobalModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
element_spec = dict(
ntp_state=dict(choices=['enabled', 'disabled']),
ptp_state=dict(choices=['enabled', 'disabled'], default='enabled'),
domain=dict(type=int),
primary_priority=dict(type=int),
secondary_priority=dict(type=int)
)
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(module_params)
self._validate_param_values(self._required_config)
def _validate_param_values(self, obj, param=None):
super(OnyxPtpGlobalModule, self).validate_param_values(obj, param)
if obj['ntp_state'] == 'enabled' and obj['ptp_state'] == 'enabled':
self._module.fail_json(msg='PTP State and NTP State Can not be enabled at the same time')
def validate_domain(self, value):
if value and not 0 <= int(value) <= 127:
self._module.fail_json(msg='domain must be between 0 and 127')
def validate_primary_priority(self, value):
if value and not 0 <= int(value) <= 255:
self._module.fail_json(msg='Primary Priority must be between 0 and 255')
def validate_secondary_priority(self, value):
if value and not 0 <= int(value) <= 255:
self._module.fail_json(msg='Secondary Priority must be between 0 and 255')
def _set_ntp_config(self, ntp_config):
ntp_config = ntp_config[0]
if not ntp_config:
return
ntp_state = ntp_config.get('NTP enabled')
if ntp_state == "yes":
self._current_config['ntp_state'] = "enabled"
else:
self._current_config['ntp_state'] = "disabled"
def _set_ptp_config(self, ptp_config):
if ptp_config is None:
self._current_config['ptp_state'] = 'disabled'
else:
self._current_config['ptp_state'] = 'enabled'
self._current_config['domain'] = int(ptp_config['Domain'])
self._current_config['primary_priority'] = int(ptp_config['Priority1'])
self._current_config['secondary_priority'] = int(ptp_config['Priority2'])
def _show_ntp_config(self):
cmd = "show ntp configured"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _show_ptp_config(self):
cmd = "show ptp clock"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
ntp_config = self._show_ntp_config()
self._set_ntp_config(ntp_config)
ptp_config = self._show_ptp_config()
self._set_ptp_config(ptp_config)
def generate_commands(self):
ntp_state = self._required_config.get("ntp_state")
if ntp_state == "enabled":
self._enable_ntp()
elif ntp_state == "disabled":
self._disable_ntp()
ptp_state = self._required_config.get("ptp_state", "enabled")
if ptp_state == "enabled":
self._enable_ptp()
else:
self._disable_ptp()
domain = self._required_config.get("domain")
if domain is not None:
curr_domain = self._current_config.get("domain")
if domain != curr_domain:
self._commands.append('ptp domain %d' % domain)
primary_priority = self._required_config.get("primary_priority")
if primary_priority is not None:
curr_primary_priority = self._current_config.get("primary_priority")
if primary_priority != curr_primary_priority:
self._commands.append('ptp priority1 %d' % primary_priority)
secondary_priority = self._required_config.get("secondary_priority")
if secondary_priority is not None:
curr_secondary_priority = self._current_config.get("secondary_priority")
if secondary_priority != curr_secondary_priority:
self._commands.append('ptp priority2 %d' % secondary_priority)
def _enable_ptp(self):
curr_ptp_state = self._current_config['ptp_state']
if curr_ptp_state == 'disabled':
self._commands.append('protocol ptp')
def _disable_ptp(self):
curr_ptp_state = self._current_config['ptp_state']
if curr_ptp_state == 'enabled':
self._commands.append('no protocol ptp')
def _enable_ntp(self):
curr_ntp_state = self._current_config.get('ntp_state')
if curr_ntp_state == 'disabled':
self._commands.append('ntp enable')
def _disable_ntp(self):
curr_ntp_state = self._current_config['ntp_state']
if curr_ntp_state == 'enabled':
self._commands.append('no ntp enable')
def main():
""" main entry point for module execution
"""
OnyxPtpGlobalModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,224 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_ptp_interface
author: 'Anas Badaha (@anasb)'
short_description: 'Configures PTP on interface'
description:
- "This module provides declarative management of PTP interfaces configuration
on Mellanox ONYX network devices."
notes:
- 'Tested on ONYX 3.6.8130'
- 'PTP Protocol must be enabled on switch.'
- 'Interface must not be a switch port interface.'
options:
name:
description:
- 'ethernet or vlan interface name that we want to configure PTP on it'
required: true
state:
description:
- 'Enable/Disable PTP on Interface'
default: enabled
choices:
- enabled
- disabled
delay_request:
description:
- 'configure PTP delay request interval, Range 0-5'
announce_interval:
description:
- 'configure PTP announce setting for interval, Range -3-1'
announce_timeout:
description:
- 'configure PTP announce setting for timeout, Range 2-10'
sync_interval:
description:
- 'configure PTP sync interval, Range -7--1'
'''
EXAMPLES = """
- name: Configure PTP interface
onyx_ptp_interface:
state: enabled
name: Eth1/1
delay_request: 0
announce_interval: -2
announce_timeout: 3
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/16 ptp enable
- interface ethernet 1/16 ptp delay-req interval 0
- interface ethernet 1/16 ptp announce interval -1
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxPtpInterfaceModule(BaseOnyxModule):
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
IF_VLAN_REGEX = re.compile(r"^Vlan (\d+)$")
IF_TYPE_ETH = "ethernet"
IF_TYPE_VLAN = "vlan"
IF_TYPE_MAP = {
IF_TYPE_ETH: IF_ETH_REGEX,
IF_TYPE_VLAN: IF_VLAN_REGEX
}
RANGE_ATTR = {
"delay_request": (0, 5),
"announce_interval": (-3, -1),
"announce_timeout": (2, 10),
"sync_interval": (-7, -1)
}
_interface_type = None
_interface_id = None
def init_module(self):
""" initialize module
"""
element_spec = dict(
name=dict(required=True),
state=dict(choices=['enabled', 'disabled'], default='enabled'),
delay_request=dict(type=int),
announce_interval=dict(type=int),
announce_timeout=dict(type=int),
sync_interval=dict(type=int)
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
@classmethod
def _get_interface_type(cls, if_name):
if_type = None
if_id = None
for interface_type, interface_regex in iteritems(cls.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, module_params):
if_name = module_params['name']
self._interface_type, self._interface_id = self._get_interface_type(if_name)
if not self._interface_id:
self._module.fail_json(
msg='unsupported interface name/type: %s' % if_name)
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(module_params)
self._set_if_type(self._required_config)
self.validate_param_values(self._required_config)
def _validate_attr_is_not_none(self, attr_name, attr_value):
if attr_value is not None:
self._module.fail_json(msg='Can not set %s value on switch while state is disabled' % attr_name)
def validate_param_values(self, obj, param=None):
if obj['state'] == 'disabled':
for attr_name in self.RANGE_ATTR:
self._validate_attr_is_not_none(attr_name, obj[attr_name])
super(OnyxPtpInterfaceModule, self).validate_param_values(obj, param)
def _validate_range(self, value, attr_name):
min_value, max_value = self.RANGE_ATTR[attr_name]
if value and not min_value <= int(value) <= max_value:
self._module.fail_json(msg='%s value must be between %d and %d' % (attr_name, min_value, max_value))
def validate_delay_request(self, value):
self._validate_range(value, "delay_request")
def validate_announce_interval(self, value):
self._validate_range(value, "announce_interval")
def validate_announce_timeout(self, value):
self._validate_range(value, "announce_timeout")
def validate_sync_interval(self, value):
self._validate_range(value, "sync_interval")
def _set_ptp_interface_config(self, ptp_interface_config):
if ptp_interface_config is None:
self._current_config['state'] = 'disabled'
return
ptp_interface_config = ptp_interface_config[0]
self._current_config['state'] = 'enabled'
self._current_config['delay_request'] = int(ptp_interface_config['Delay request interval(log mean)'])
self._current_config['announce_interval'] = int(ptp_interface_config['Announce interval(log mean)'])
self._current_config['announce_timeout'] = int(ptp_interface_config['Announce receipt time out'])
self._current_config['sync_interval'] = int(ptp_interface_config['Sync interval(log mean)'])
def _show_ptp_interface_config(self):
cmd = "show ptp interface %s %s" % (self._interface_type, self._interface_id)
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
ptp_interface_config = self._show_ptp_interface_config()
self._set_ptp_interface_config(ptp_interface_config)
def _generate_attr_command(self, attr_name, attr_cmd_name):
attr_val = self._required_config.get(attr_name)
if attr_val is not None:
curr_val = self._current_config.get(attr_name)
if attr_val != curr_val:
self._commands.append(
'interface %s %s ptp %s %d' % (self._interface_type, self._interface_id, attr_cmd_name, attr_val))
def generate_commands(self):
state = self._required_config.get("state", "enabled")
self._gen_ptp_commands(state)
self._generate_attr_command("delay_request", "delay-req interval")
self._generate_attr_command("announce_interval", "announce interval")
self._generate_attr_command("announce_timeout", "announce timeout")
self._generate_attr_command("sync_interval", "sync interval")
def _add_if_ptp_cmd(self, req_state):
if req_state == 'enabled':
if_ptp_cmd = 'interface %s %s ptp enable' % (self._interface_type, self._interface_id)
else:
if_ptp_cmd = 'no interface %s %s ptp enable' % (self._interface_type, self._interface_id)
self._commands.append(if_ptp_cmd)
def _gen_ptp_commands(self, req_state):
curr_state = self._current_config.get('state')
if curr_state != req_state:
self._add_if_ptp_cmd(req_state)
def main():
""" main entry point for module execution
"""
OnyxPtpInterfaceModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,231 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_qos
author: "Anas Badaha (@anasb)"
short_description: Configures QoS
description:
- This module provides declarative management of Onyx QoS configuration
on Mellanox ONYX network devices.
notes:
- Tested on ONYX 3.6.8130
options:
interfaces:
description:
- list of interfaces name.
required: true
trust:
description:
- trust type.
choices: ['L2', 'L3', 'both']
default: L2
rewrite_pcp:
description:
- rewrite with type pcp.
choices: ['enabled', 'disabled']
default: disabled
rewrite_dscp:
description:
- rewrite with type dscp.
choices: ['enabled', 'disabled']
default: disabled
'''
EXAMPLES = """
- name: Configure QoS
onyx_QoS:
interfaces:
- Mpo7
- Mpo7
trust: L3
rewrite_pcp: disabled
rewrite_dscp: enabled
- name: Configure QoS
onyx_QoS:
interfaces:
- Eth1/1
- Eth1/2
trust: both
rewrite_pcp: disabled
rewrite_dscp: enabled
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/16 qos trust L3
- interface mlag-port-channel 7 qos trust L3
- interface port-channel 1 qos trust L3
- interface mlag-port-channel 7 qos trust L2
- interface mlag-port-channel 7 qos rewrite dscp
- interface ethernet 1/16 qos rewrite pcp
- interface ethernet 1/1 no qos rewrite pcp
"""
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxQosModule(BaseOnyxModule):
TRUST_CMD = "interface {0} {1} qos trust {2}"
NO_REWRITE_PCP_CMD = "interface {0} {1} no qos rewrite pcp"
NO_REWRITE_DSCP_CMD = "interface {0} {1} no qos rewrite dscp"
REWRITE_PCP_CMD = "interface {0} {1} qos rewrite pcp"
REWRITE_DSCP_CMD = "interface {0} {1} qos rewrite dscp"
REWRITE_PCP = "pcp"
REWRITE_DSCP = "dscp"
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
IF_PO_REGEX = re.compile(r"^Po(\d+)$")
MLAG_NAME_REGEX = re.compile(r"^Mpo(\d+)$")
IF_TYPE_ETH = "ethernet"
PORT_CHANNEL = "port-channel"
MLAG_PORT_CHANNEL = "mlag-port-channel"
IF_TYPE_MAP = {
IF_TYPE_ETH: IF_ETH_REGEX,
PORT_CHANNEL: IF_PO_REGEX,
MLAG_PORT_CHANNEL: MLAG_NAME_REGEX
}
def init_module(self):
""" initialize module
"""
element_spec = dict(
interfaces=dict(type='list', required=True),
trust=dict(choices=['L2', 'L3', 'both'], default='L2'),
rewrite_pcp=dict(choices=['enabled', 'disabled'], default='disabled'),
rewrite_dscp=dict(choices=['enabled', 'disabled'], default='disabled')
)
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(module_params)
self.validate_param_values(self._required_config)
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_interface_qos_config(self, interface_qos_config, interface, if_type, if_id):
interface_qos_config = interface_qos_config[0].get(interface)
trust = interface_qos_config[0].get("Trust mode")
rewrite_dscp = interface_qos_config[0].get("DSCP rewrite")
rewrite_pcp = interface_qos_config[0].get("PCP,DEI rewrite")
self._current_config[interface] = dict(trust=trust, rewrite_dscp=rewrite_dscp,
rewrite_pcp=rewrite_pcp, if_type=if_type, if_id=if_id)
def _show_interface_qos(self, if_type, interface):
cmd = "show qos interface {0} {1}".format(if_type, interface)
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
for interface in self._required_config.get("interfaces"):
if_type, if_id = self._get_interface_type(interface)
if not if_id:
self._module.fail_json(
msg='unsupported interface: {0}'.format(interface))
interface_qos_config = self._show_interface_qos(if_type, if_id)
if interface_qos_config is not None:
self._set_interface_qos_config(interface_qos_config, interface, if_type, if_id)
else:
self._module.fail_json(
msg='Interface {0} does not exist on switch'.format(interface))
def generate_commands(self):
trust = self._required_config.get("trust")
rewrite_pcp = self._required_config.get("rewrite_pcp")
rewrite_dscp = self._required_config.get("rewrite_dscp")
for interface in self._required_config.get("interfaces"):
ignored1, ignored2, current_trust, if_type, if_id = self._get_current_rewrite_config(interface)
self._add_interface_trust_cmds(if_type, if_id, interface, trust, current_trust)
self._add_interface_rewrite_cmds(if_type, if_id, interface,
rewrite_pcp, rewrite_dscp)
def _get_current_rewrite_config(self, interface):
current_interface_qos_config = self._current_config.get(interface)
current_rewrite_pcp = current_interface_qos_config.get('rewrite_pcp')
current_rewrite_dscp = current_interface_qos_config.get('rewrite_dscp')
if_type = current_interface_qos_config.get("if_type")
if_id = current_interface_qos_config.get("if_id")
current_trust = current_interface_qos_config.get('trust')
return current_rewrite_pcp, current_rewrite_dscp, current_trust, if_type, if_id
def _add_interface_trust_cmds(self, if_type, if_id, interface, trust, current_trust):
current_rewrite_pcp, current_rewrite_dscp, ignored1, ignored2, ignored3 = self._get_current_rewrite_config(
interface)
if trust == "L3" and trust != current_trust:
self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_DSCP, current_rewrite_dscp)
self._commands.append(self.TRUST_CMD.format(if_type, if_id, trust))
elif trust == "L2" and trust != current_trust:
self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_PCP, current_rewrite_pcp)
self._commands.append(self.TRUST_CMD.format(if_type, if_id, trust))
elif trust == "both" and trust != current_trust:
self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_DSCP, current_rewrite_dscp)
self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_PCP, current_rewrite_pcp)
self._commands.append(self.TRUST_CMD.format(if_type, if_id, trust))
def _add_interface_rewrite_cmds(self, if_type, if_id, interface, rewrite_pcp, rewrite_dscp):
current_rewrite_pcp, current_rewrite_dscp, ignored1, ignored2, ignored3 = self._get_current_rewrite_config(
interface)
if rewrite_pcp == "enabled" and rewrite_pcp != current_rewrite_pcp:
self._commands.append(self.REWRITE_PCP_CMD.format(if_type, if_id))
elif rewrite_pcp == "disabled" and rewrite_pcp != current_rewrite_pcp:
self._commands.append(self.NO_REWRITE_PCP_CMD.format(if_type, if_id))
if rewrite_dscp == "enabled" and rewrite_dscp != current_rewrite_dscp:
self._commands.append(self.REWRITE_DSCP_CMD.format(if_type, if_id))
elif rewrite_dscp == "disabled" and rewrite_dscp != current_rewrite_dscp:
self._commands.append(self.NO_REWRITE_DSCP_CMD.format(if_type, if_id))
def _add_no_rewrite_cmd(self, if_type, if_id, interface, rewrite_type, current_rewrite):
if rewrite_type == self.REWRITE_PCP and current_rewrite == "enabled":
self._commands.append(self.NO_REWRITE_PCP_CMD.format(if_type, if_id))
self._current_config[interface]["rewrite_pcp"] = "disabled"
elif rewrite_type == self.REWRITE_DSCP and current_rewrite == "enabled":
self._commands.append(self.NO_REWRITE_DSCP_CMD.format(if_type, if_id))
self._current_config[interface]["rewrite_dscp"] = "disabled"
def main():
""" main entry point for module execution
"""
OnyxQosModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,423 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_snmp
version_added: '0.2.0'
author: "Sara-Touqan (@sarato)"
short_description: Manages SNMP general configurations on Mellanox ONYX network devices
description:
- This module provides declarative management of SNMP
on Mellanox ONYX network devices.
options:
state_enabled:
description:
- Enables/Disables the state of the SNMP configuration.
type: bool
contact_name:
description:
- Sets the SNMP contact name.
type: str
location:
description:
- Sets the SNMP location.
type: str
communities_enabled:
description:
- Enables/Disables community-based authentication on the system.
type: bool
multi_communities_enabled:
description:
- Enables/Disables multiple communities to be configured.
type: bool
snmp_communities:
type: list
description:
- List of snmp communities
suboptions:
community_name:
description:
- Configures snmp community name.
required: true
type: str
community_type:
description:
- Add this community as either a read-only or read-write community.
choices: ['read-only', 'read-write']
type: str
state:
description:
- Used to decide if you want to delete the given snmp community or not
choices: ['present', 'absent']
type: str
notify_enabled:
description:
- Enables/Disables sending of SNMP notifications (traps and informs) from thee system.
type: bool
notify_port:
description:
- Sets the default port to which notifications are sent.
type: str
notify_community:
description:
- Sets the default community for SNMP v1 and v2c notifications sent to hosts which do not have a community override set.
type: str
notify_send_test:
description:
- Sends a test notification.
type: str
choices: ['yes','no']
notify_event:
description:
- Specifys which events will be sent as SNMP notifications.
type: str
choices: ['asic-chip-down', 'dcbx-pfc-port-oper-state-trap', 'insufficient-power', 'mstp-new-bridge-root',
'ospf-lsdb-approaching-overflow', 'sm-stop', 'user-logout', 'cli-line-executed', 'dcbx-pfc-port-peer-state-trap',
'interface-down', 'mstp-new-root-port', 'ospf-lsdb-overflow', 'snmp-authtrap', 'xstp-new-root-bridge',
'cpu-util-high', 'disk-io-high', 'interface-up', 'mstp-topology-change', 'ospf-nbr-state-change',
'temperature-too-high', 'xstp-root-port-change', 'dcbx-ets-module-state-change', 'disk-space-low',
'internal-bus-error', 'netusage-high', 'paging-high', 'topology_change', 'xstp-topology-change',
'dcbx-ets-port-admin-state-trap', 'entity-state-change', 'internal-link-speed-mismatch', 'new_root',
'power-redundancy-mismatch', 'unexpected-cluster-join', 'dcbx-ets-port-oper-state-trap', 'expected-shutdown',
'liveness-failure', 'ospf-auth-fail', 'process-crash', 'unexpected-cluster-leave', 'dcbx-ets-port-peer-state-trap',
'health-module-status', 'low-power', 'ospf-config-error', 'process-exit', 'unexpected-cluster-size',
'dcbx-pfc-module-state-change', 'insufficient-fans', 'low-power-recover', 'ospf-if-rx-bad-packet',
'sm-restart', 'unexpected-shutdown', 'dcbx-pfc-port-admin-state-trap', 'insufficient-fans-recover', 'memusage-high',
'ospf-if-state-change', 'sm-start', 'user-login']
engine_id_reset:
description:
- Sets SNMPv3 engineID to node unique value.
type: bool
snmp_permissions:
type: list
description:
- Allow SNMPSET requests for items in a MIB.
suboptions:
state_enabled:
description:
- Enables/Disables the request.
required: true
type: bool
permission_type:
description:
- Configures the request type.
choices: ['MELLANOX-CONFIG-DB-MIB', 'MELLANOX-EFM-MIB','MELLANOX-POWER-CYCLE','MELLANOX-SW-UPDATE','RFC1213-MIB']
type: str
'''
EXAMPLES = """
- name: Configure SNMP
onyx_snmp:
state_enabled: yes
contact_name: sara
location: Nablus
communities_enabled: no
multi_communities_enabled: no
notify_enabled: yes
notify_port: 1
notify_community: community_1
notify_send_test: yes
notify_event: temperature-too-high
snmp_communities:
- community_name: public
community_type: read-only
state: absent
snmp_permissions:
- state_enabled: yes
permission_type: MELLANOX-CONFIG-DB-MIB
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always.
type: list
sample:
- snmp-server enable
- no snmp-server enable
- snmp-server location <location_name>
- snmp-server contact <contact_name>
- snmp-server enable communities
- no snmp-server enable communities
- snmp-server enable mult-communities
- no snmp-server enable mult-communities
- snmp-server enable notify
- snmp-server notify port <port_number>
- snmp-server notify community <community_name>
- snmp-server notify send-test
- snmp-server notify event <event_name>
- snmp-server enable set-permission <permission_type>
- no snmp-server enable set-permission <permission_type>
- snmp-server community <community_name> <community_type>
- no snmp-server community <community_name>.
- snmp-server engineID reset.
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
class OnyxSNMPModule(BaseOnyxModule):
def init_module(self):
""" module initialization
"""
community_spec = dict(community_name=dict(required=True),
community_type=dict(choices=['read-only', 'read-write']),
state=dict(choices=['present', 'absent']))
snmp_permission_spec = dict(state_enabled=dict(type='bool', required=True),
permission_type=dict(choices=['MELLANOX-CONFIG-DB-MIB', 'MELLANOX-EFM-MIB', 'MELLANOX-POWER-CYCLE',
'MELLANOX-SW-UPDATE', 'RFC1213-MIB']))
event_choices = ['asic-chip-down', 'dcbx-pfc-port-oper-state-trap', 'insufficient-power', 'mstp-new-bridge-root',
'ospf-lsdb-approaching-overflow', 'sm-stop', 'user-logout', 'cli-line-executed', 'dcbx-pfc-port-peer-state-trap',
'interface-down', 'mstp-new-root-port', 'ospf-lsdb-overflow', 'snmp-authtrap', 'xstp-new-root-bridge',
'cpu-util-high', 'disk-io-high', 'interface-up', 'mstp-topology-change', 'ospf-nbr-state-change',
'temperature-too-high', 'xstp-root-port-change', 'dcbx-ets-module-state-change', 'disk-space-low',
'internal-bus-error', 'netusage-high', 'paging-high', 'topology_change', 'xstp-topology-change',
'dcbx-ets-port-admin-state-trap', 'entity-state-change', 'internal-link-speed-mismatch', 'new_root',
'power-redundancy-mismatch', 'unexpected-cluster-join', 'dcbx-ets-port-oper-state-trap', 'expected-shutdown',
'liveness-failure', 'ospf-auth-fail', 'process-crash', 'unexpected-cluster-leave', 'dcbx-ets-port-peer-state-trap',
'health-module-status', 'low-power', 'ospf-config-error', 'process-exit', 'unexpected-cluster-size',
'dcbx-pfc-module-state-change', 'insufficient-fans', 'low-power-recover', 'ospf-if-rx-bad-packet',
'sm-restart', 'unexpected-shutdown', 'dcbx-pfc-port-admin-state-trap', 'insufficient-fans-recover', 'memusage-high',
'ospf-if-state-change', 'sm-start', 'user-login']
element_spec = dict(
state_enabled=dict(type='bool'),
contact_name=dict(type='str'),
location=dict(type='str'),
communities_enabled=dict(type='bool'),
multi_communities_enabled=dict(type='bool'),
snmp_communities=dict(type='list', elements='dict', options=community_spec),
notify_enabled=dict(type='bool'),
notify_port=dict(type='str'),
notify_community=dict(type='str'),
notify_send_test=dict(type='str', choices=['yes', 'no']),
notify_event=dict(type='str', choices=event_choices),
engine_id_reset=dict(type='bool'),
snmp_permissions=dict(type='list', elements='dict', options=snmp_permission_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(module_params)
self.validate_param_values(self._required_config)
def _show_snmp_config(self):
show_cmds = []
cmd = "show snmp"
show_cmds.append(show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False))
cmd = "show running-config | include snmp"
show_cmds.append(show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False))
return show_cmds
def _set_snmp_config(self, all_snmp_config):
ro_communities_list = []
rw_communities_list = []
snmp_config = all_snmp_config[0]
if not snmp_config:
return
if snmp_config.get("SNMP enabled") == 'yes':
self._current_config['state_enabled'] = True
else:
self._current_config['state_enabled'] = False
self._current_config['contact_name'] = snmp_config.get("System contact")
self._current_config['location'] = snmp_config.get("System location")
curr_ro_comm = snmp_config.get("Read-only community")
if curr_ro_comm:
ro_arr = curr_ro_comm.split(' ')
rw_arr = snmp_config.get("Read-write community").split(' ')
ro_communities_list = ro_arr[0]
rw_communities_list = rw_arr[0]
if (len(ro_arr) == 2):
self._current_config['communities_enabled'] = False
else:
self._current_config['communities_enabled'] = True
else:
read_only_communities = all_snmp_config[1]
read_write_communities = all_snmp_config[2]
if not read_only_communities:
return
read_only_comm = read_only_communities.get("Read-only communities")
if read_only_comm:
self._current_config['communities_enabled'] = True
ro_communities_list = read_only_comm[0].get("Lines")
else:
self._current_config['communities_enabled'] = False
ro_comm_disabled = read_only_communities.get("Read-only communities (DISABLED)")
if ro_comm_disabled:
ro_communities_list = ro_comm_disabled[0].get("Lines")
if not read_write_communities:
return
read_write_comm = read_write_communities.get("Read-write communities")
if read_write_comm:
self._current_config['communities_enabled'] = True
rw_communities_list = read_write_comm[0].get("Lines")
else:
self._current_config['communities_enabled'] = False
rw_comm_disabled = read_write_communities.get("Read-write communities (DISABLED)")
if rw_comm_disabled:
rw_communities_list = rw_comm_disabled[0].get("Lines")
self._current_config['ro_communities_list'] = ro_communities_list
self._current_config['rw_communities_list'] = rw_communities_list
def _set_snmp_running_config(self, snmp_running_config):
self._current_config['multi_comm_enabled'] = True
self._current_config['notify_enabled'] = True
curr_config_arr = []
snmp_lines = snmp_running_config.get('Lines')
for runn_config in snmp_lines:
curr_config_arr.append(runn_config.strip())
if 'no snmp-server enable mult-communities' in snmp_lines:
self._current_config['multi_comm_enabled'] = False
if 'no snmp-server enable notify' in snmp_lines:
self._current_config['notify_enabled'] = False
self._current_config['snmp_running_config'] = curr_config_arr
def load_current_config(self):
self._current_config = dict()
snmp_config = self._show_snmp_config()
if snmp_config[0]:
self._set_snmp_config(snmp_config[0])
if snmp_config[1]:
self._set_snmp_running_config(snmp_config[1])
def generate_commands(self):
current_state = self._current_config.get("state_enabled")
state = current_state
req_state = self._required_config.get("state_enabled")
if req_state is not None:
state = req_state
if state is not None:
if current_state != state:
if state is True:
self._commands.append('snmp-server enable')
else:
self._commands.append('no snmp-server enable')
contact_name = self._required_config.get("contact_name")
if contact_name:
current_contact_name = self._current_config.get("contact_name")
if contact_name is not None:
if current_contact_name != contact_name:
self._commands.append('snmp-server contact {0}' .format(contact_name))
location = self._required_config.get("location")
if location:
current_location = self._current_config.get("location")
if location is not None:
if current_location != location:
self._commands.append('snmp-server location {0}' .format(location))
communities_enabled = self._required_config.get("communities_enabled")
if communities_enabled is not None:
current_communities_enabled = self._current_config.get("communities_enabled")
if communities_enabled is not None:
if current_communities_enabled != communities_enabled:
if communities_enabled is True:
self._commands.append('snmp-server enable communities')
else:
self._commands.append('no snmp-server enable communities')
ro_communities = self._current_config.get("ro_communities_list")
rw_communities = self._current_config.get("rw_communities_list")
snmp_communities = self._required_config.get("snmp_communities")
if snmp_communities:
if snmp_communities is not None:
for community in snmp_communities:
community_name = community.get("community_name")
state = community.get("state")
if state:
if state == 'absent':
self._commands.append('no snmp-server community {0}' .format(community_name))
continue
community_type = community.get("community_type")
if community_type:
if community_type == 'read-only':
if community_name not in ro_communities:
self._commands.append('snmp-server community {0} ro' .format(community_name))
else:
if community_name not in rw_communities:
self._commands.append('snmp-server community {0} rw' .format(community_name))
else:
if community_name not in ro_communities:
self._commands.append('snmp-server community {0}' .format(community_name))
engine_id_reset = self._required_config.get("engine_id_reset")
if engine_id_reset is not None:
if engine_id_reset:
self._commands.append('snmp-server engineID reset')
current_multi_comm_state = self._current_config.get("multi_comm_enabled")
multi_communities_enabled = self._required_config.get("multi_communities_enabled")
if multi_communities_enabled is not None:
if current_multi_comm_state != multi_communities_enabled:
if multi_communities_enabled is True:
self._commands.append('snmp-server enable mult-communities')
else:
self._commands.append('no snmp-server enable mult-communities')
notify_enabled = self._required_config.get("notify_enabled")
if notify_enabled is not None:
current_notify_state = self._current_config.get("notify_enabled")
if current_notify_state != notify_enabled:
if notify_enabled is True:
self._commands.append('snmp-server enable notify')
else:
self._commands.append('no snmp-server enable notify')
snmp_permissions = self._required_config.get("snmp_permissions")
if snmp_permissions is not None:
for permission in snmp_permissions:
permission_type = permission.get('permission_type')
if permission.get('state_enabled') is True:
self._commands.append('snmp-server enable set-permission {0}' .format(permission_type))
else:
self._commands.append('no snmp-server enable set-permission {0}' .format(permission_type))
snmp_running_config = self._current_config.get("snmp_running_config")
notify_port = self._required_config.get("notify_port")
if notify_port is not None:
notified_cmd = 'snmp-server notify port {0}' .format(notify_port)
if notified_cmd not in snmp_running_config:
self._commands.append('snmp-server notify port {0}' .format(notify_port))
notify_community = self._required_config.get("notify_community")
if notify_community is not None:
notified_cmd = 'snmp-server notify community {0}' .format(notify_community)
if notified_cmd not in snmp_running_config:
self._commands.append('snmp-server notify community {0}' .format(notify_community))
notify_send_test = self._required_config.get("notify_send_test")
if notify_send_test is not None:
if notify_send_test == 'yes':
self._commands.append('snmp-server notify send-test')
notify_event = self._required_config.get("notify_event")
if notify_event is not None:
self._commands.append('snmp-server notify event {0}' .format(notify_event))
def main():
""" main entry point for module execution
"""
OnyxSNMPModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,421 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_snmp_hosts
version_added: '0.2.0'
author: "Sara Touqan (@sarato)"
short_description: Configures SNMP host parameters
description:
- This module provides declarative management of SNMP hosts protocol params
on Mellanox ONYX network devices.
options:
hosts:
type: list
description:
- List of snmp hosts
suboptions:
name:
description:
- Specifies the name of the host.
required: true
type: str
enabled:
description:
- Temporarily Enables/Disables sending of all notifications to this host.
type: bool
notification_type:
description:
- Configures the type of sending notification to the specified host.
choices: ['trap', 'inform']
type: str
port:
description:
- Overrides default target port for this host.
type: str
version:
description:
- Specifys SNMP version of informs to send.
choices: ['1', '2c', '3']
type: str
user_name:
description:
- Specifys username for this inform sink.
type: str
auth_type:
description:
- Configures SNMP v3 security parameters, specifying passwords in a nother parameter (auth_password) (passwords are always stored encrypted).
choices: ['md5', 'sha', 'sha224', 'sha256', 'sha384', 'sha512']
type: str
auth_password:
description:
- The password needed to configure the auth type.
type: str
privacy_type:
description:
- Specifys SNMP v3 privacy settings for this user.
choices: ['3des', 'aes-128', 'aes-192', 'aes-192-cfb', 'aes-256', 'aes-256-cfb', 'des']
type: str
privacy_password:
description:
- The password needed to configure the privacy type.
type: str
state:
description:
- Used to decide if you want to delete the specified host or not.
choices: ['present' , 'absent']
type: str
'''
EXAMPLES = """
- name: Enables snmp host
onyx_snmp_hosts:
hosts:
- name: 1.1.1.1
enabled: true
- name: Configures snmp host with version 2c
onyx_snmp_hosts:
hosts:
- name: 2.3.2.4
enabled: true
notification_type: trap
port: 66
version: 2c
- name: Configures snmp host with version 3 and configures it with user as sara
onyx_snmp_hosts:
hosts:
- name: 2.3.2.4
enabled: true
notification_type: trap
port: 66
version: 3
user_name: sara
auth_type: sha
auth_password: jnbdfijbdsf
privacy_type: 3des
privacy_password: nojfd8uherwiugfh
- name: Deletes the snmp host
onyx_snmp_hosts:
hosts:
- name: 2.3.2.4
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- snmp-server host <host_name> disable
- no snmp-server host <host_name> disable
- snmp-server host <host_name> informs port <port_number> version <version_number>
- snmp-server host <host_name> traps port <port_number> version <version_number>
- snmp-server host <host_name> informs port <port_number> version <version_number> user <user_name> auth <auth_type>
<auth_password> priv <privacy_type> <privacy_password>
- snmp-server host <host_name> traps port <port_number> version <version_number> user <user_name> auth <auth_type>
<auth_password> priv <privacy_type> <privacy_password>
- no snmp-server host <host_name>.
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxSNMPHostsModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
host_spec = dict(name=dict(required=True),
enabled=dict(type='bool'),
notification_type=dict(type='str', choices=['trap', 'inform']),
port=dict(type='str'),
version=dict(type='str', choices=['1', '2c', '3']),
user_name=dict(type='str'),
auth_type=dict(type='str', choices=['md5', 'sha', 'sha224', 'sha256', 'sha384', 'sha512']),
privacy_type=dict(type='str', choices=['3des', 'aes-128', 'aes-192', 'aes-192-cfb', 'aes-256', 'aes-256-cfb', 'des']),
privacy_password=dict(type='str', no_log=True),
auth_password=dict(type='str', no_log=True),
state=dict(type='str', choices=['present', 'absent'])
)
element_spec = dict(
hosts=dict(type='list', elements='dict', options=host_spec),
)
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True)
def validate_snmp_required_params(self):
req_hosts = self._required_config.get("hosts")
if req_hosts:
for host in req_hosts:
version = host.get('version')
if version:
if version == '3':
if host.get('user_name') is None or host.get('auth_type') is None or host.get('auth_password') is None:
self._module.fail_json(msg='user_name, auth_type and auth_password are required when version number is 3.')
if host.get('notification_type') is not None:
if host.get('version') is None or host.get('port') is None:
self._module.fail_json(msg='port and version are required when notification_type is provided.')
if host.get('auth_type') is not None:
if host.get('auth_password') is None:
self._module.fail_json(msg='auth_password is required when auth_type is provided.')
if host.get('privacy_type') is not None:
if host.get('privacy_password') is None:
self._module.fail_json(msg='privacy_password is required when privacy_type is provided.')
def get_required_config(self):
module_params = self._module.params
self._required_config = dict(module_params)
self.validate_param_values(self._required_config)
self.validate_snmp_required_params()
def _set_host_config(self, hosts_config):
hosts = hosts_config.get('Notification sinks')
if hosts[0].get('Lines'):
self._current_config['current_hosts'] = dict()
self._current_config['host_names'] = []
return
current_hosts = dict()
host_names = []
for host in hosts:
host_info = dict()
for host_name in host:
host_names.append(host_name)
enabled = True
first_entry = host.get(host_name)[0]
if first_entry:
if first_entry.get('Enabled') == 'no':
enabled = False
notification_type = first_entry.get('Notification type')
notification_type = notification_type.split()
host_info['notification_type'] = notification_type[2]
version = notification_type[1][1:]
host_info['port'] = first_entry.get('Port')
host_info['name'] = host_name
host_info['enabled'] = enabled
host_info['version'] = version
if first_entry.get('Community') is None:
if len(first_entry) == 8:
host_info['user_name'] = first_entry.get('Username')
host_info['auth_type'] = first_entry.get('Authentication type')
host_info['privacy_type'] = first_entry.get('Privacy type')
elif len(host.get(host_name)) == 2:
second_entry = host.get(host_name)[1]
host_info['user_name'] = second_entry.get('Username')
host_info['auth_type'] = second_entry.get('Authentication type')
host_info['privacy_type'] = second_entry.get('Privacy type')
else:
host_info['user_name'] = ''
host_info['auth_type'] = ''
host_info['privacy_type'] = ''
else:
host_info['user_name'] = ''
host_info['auth_type'] = ''
host_info['privacy_type'] = ''
current_hosts[host_name] = host_info
self._current_config['current_hosts'] = current_hosts
self._current_config['host_names'] = host_names
def _show_hosts_config(self):
cmd = "show snmp host"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
hosts_config = self._show_hosts_config()
if hosts_config[1]:
self._set_host_config(hosts_config[1])
def generate_snmp_commands_with_current_config(self, host):
host_id = host.get('name')
host_notification_type = host.get('notification_type')
host_enabled = host.get("enabled")
host_port = host.get('port')
host_version = host.get('version')
host_username = host.get('user_name')
host_auth_type = host.get('auth_type')
host_auth_pass = host.get('auth_password')
host_priv_type = host.get('privacy_type')
host_priv_pass = host.get('privacy_password')
present_state = host.get('state')
current_hosts = self._current_config.get("current_hosts")
current_entry = current_hosts.get(host_id)
if present_state is not None:
if present_state == 'absent':
self._commands.append('no snmp-server host {0}' .format(host_id))
return
if host_enabled is not None:
if current_entry.get('enabled') != host_enabled:
if host_enabled is True:
self._commands.append('no snmp-server host {0} disable' .format(host_id))
else:
self._commands.append('snmp-server host {0} disable' .format(host_id))
if host_notification_type is not None:
current_port = current_entry.get('port')
current_version = current_entry.get('version')
current_priv_type = current_entry.get('privacy_type')
current_username = current_entry.get('user_name')
current_auth_type = current_entry.get('auth_type')
current_noti_type = current_entry.get('notification_type')
if host_port is not None:
if host_version is not None:
if host_version == '3':
if (host_priv_type is not None and host_priv_pass is not None):
if((current_noti_type != host_notification_type) or
((current_port != host_port)) or
(current_version != host_version) or
(current_priv_type != host_priv_type) or
(current_username != host_username) or
(current_auth_type != host_auth_type)):
self._commands.append('snmp-server host {0} {1}s port {2} version {3} user {4} auth {5} {6} priv {7} {8}'
.format(host_id, host_notification_type, host_port,
host_version, host_username, host_auth_type, host_auth_pass,
host_priv_type, host_priv_pass))
else:
if((current_noti_type != host_notification_type) or
((current_port != host_port)) or
(current_version != host_version) or
(current_username != host_username) or
(current_auth_type != host_auth_type)):
self._commands.append('snmp-server host {0} {1}s port {2} version {3} user {4} auth {5} {6}'
.format(host_id, host_notification_type,
host_port, host_version, host_username,
host_auth_type, host_auth_pass))
else:
if((current_noti_type != host_notification_type) or
((current_port != host_port)) or
(current_version != host_version)):
self._commands.append('snmp-server host {0} {1}s port {2} version {3}'
.format(host_id, host_notification_type,
host_port, host_version))
else:
if ((current_noti_type != host_notification_type) or
((current_port != host_port))):
self._commands.append('snmp-server host {0} {1}s port {2}'
.format(host_id, host_notification_type, host_port))
else:
if host_version is not None:
if host_version == '3':
if (host_priv_type is not None and host_priv_pass is not None):
if ((current_noti_type != host_notification_type) or
((current_version != host_version)) or
(current_username != host_username) or
((current_auth_type != host_auth_type)) or
(current_priv_type != host_priv_type)):
self._commands.append('snmp-server host {0} {1}s version {2} user {3} auth {4} {5} priv {6} {7}'
.format(host_id, host_notification_type, host_version, host_username,
host_auth_type, host_auth_pass, host_priv_type, host_priv_pass))
else:
if ((current_noti_type != host_notification_type) or
((current_version != host_version)) or
(current_username != host_username) or
((current_auth_type != host_auth_type))):
self._commands.append('snmp-server host {0} {1}s version {2} user {3} auth {4} {5}'
.format(host_id, host_notification_type,
host_version, host_username, host_auth_type, host_auth_pass))
else:
if ((current_noti_type != host_notification_type) or
((current_version != host_version))):
self._commands.append('snmp-server host {0} {1}s version {2}' .format(host_id,
host_notification_type, host_version))
def generate_snmp_commands_without_current_config(self, host):
host_id = host.get('name')
host_notification_type = host.get('notification_type')
host_enabled = host.get("enabled")
host_port = host.get('port')
host_version = host.get('version')
host_username = host.get('user_name')
host_auth_type = host.get('auth_type')
host_auth_pass = host.get('auth_password')
host_priv_type = host.get('privacy_type')
host_priv_pass = host.get('privacy_password')
present_state = host.get('state')
present_state = host.get('state')
if present_state is not None:
if present_state == 'absent':
return
if host_enabled is not None:
if host_enabled is True:
self._commands.append('no snmp-server host {0} disable' .format(host_id))
else:
self._commands.append('snmp-server host {0} disable' .format(host_id))
if host_notification_type is not None:
if host_port is not None:
if host_version is not None:
if host_version == '3':
if (host_priv_type is not None and host_priv_pass is not None):
self._commands.append('snmp-server host {0} {1}s port {2} version {3} user {4} auth {5} {6} priv {7} {8}'
.format(host_id, host_notification_type, host_port, host_version, host_username,
host_auth_type, host_auth_pass, host_priv_type, host_priv_pass))
else:
self._commands.append('snmp-server host {0} {1}s port {2} version {3} user {4} auth {5} {6}'
.format(host_id, host_notification_type, host_port, host_version, host_username,
host_auth_type, host_auth_pass))
else:
self._commands.append('snmp-server host {0} {1}s port {2} version {3}' .format(host_id,
host_notification_type, host_port, host_version))
else:
self._commands.append('snmp-server host {0} {1}s port {2}' .format(host_id,
host_notification_type, host_port))
else:
if host_version is not None:
if host_version == '3':
if (host_priv_type is not None and host_priv_pass is not None):
self._commands.append('snmp-server host {0} {1}s version {2} user {3} auth {4} {5} priv {6} {7}'
.format(host_id, host_notification_type, host_version, host_username,
host_auth_type, host_auth_pass, host_priv_type, host_priv_pass))
else:
self._commands.append('snmp-server host {0} {1}s version {2} user {3} auth {4} {5}' .format(host_id,
host_notification_type, host_version, host_username,
host_auth_type, host_auth_pass))
else:
self._commands.append('snmp-server host {0} {1}s version {2}' .format(host_id,
host_notification_type, host_version))
def generate_commands(self):
req_hosts = self._required_config.get("hosts")
host_names = self._current_config.get("host_names")
if req_hosts:
for host in req_hosts:
host_id = host.get('name')
if host_id:
if host_names and (host_id in host_names):
self.generate_snmp_commands_with_current_config(host)
else:
self.generate_snmp_commands_without_current_config(host)
def main():
""" main entry point for module execution
"""
OnyxSNMPHostsModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,274 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_snmp_users
version_added: '0.2.0'
author: "Sara Touqan (@sarato)"
short_description: Configures SNMP User parameters
description:
- This module provides declarative management of SNMP Users protocol params
on Mellanox ONYX network devices.
options:
users:
type: list
description:
- List of snmp users
suboptions:
name:
description:
- Specifies the name of the user.
required: true
type: str
enabled:
description:
- Enables/Disables SNMP v3 access for the user.
type: bool
set_access_enabled:
description:
- Enables/Disables SNMP SET requests for the user.
type: bool
require_privacy:
description:
- Enables/Disables the Require privacy (encryption) for requests from this user
type: bool
auth_type:
description:
- Configures the hash type used to configure SNMP v3 security parameters.
choices: ['md5', 'sha', 'sha224', 'sha256', 'sha384', 'sha512']
type: str
auth_password:
description:
- The password needed to configure the hash type.
type: str
capability_level:
description:
- Sets capability level for SET requests.
choices: ['admin','monitor','unpriv','v_admin']
type: str
'''
EXAMPLES = """
- name: Enables snmp user
onyx_snmp_users:
users:
- name: sara
enabled: true
- name: Enables snmp set requests
onyx_snmp_users:
users:
- name: sara
set_access_enabled: yes
- name: Enables user require privacy
onyx_snmp_users:
users:
- name: sara
require_privacy: true
- name: Configures user hash type
onyx_snmp_users:
users:
- auth_type: md5
auth_password: 1297sara1234sara
- name: Configures user capability_level
onyx_snmp_users:
users:
- name: sara
capability_level: admin
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- snmp-server user <user_name> v3 enable
- no snmp-server user <user_name> v3 enable
- snmp-server user <user_name> v3 enable sets
- no snmp-server user <user_name> v3 enable sets
- snmp-server user <user_name> v3 require-privacy
- no snmp-server user <user_name> v3 require-privacy
- snmp-server user <user_name> v3 capability <capability_level>
- snmp-server user <user_name> v3 auth <hash_type> <password>
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxSNMPUsersModule(BaseOnyxModule):
def init_module(self):
""" initialize module
"""
user_spec = dict(name=dict(required=True),
enabled=dict(type='bool'),
set_access_enabled=dict(type='bool'),
require_privacy=dict(type='bool'),
auth_type=dict(type='str', choices=['md5', 'sha', 'sha224', 'sha256', 'sha384', 'sha512']),
auth_password=dict(type='str'),
capability_level=dict(type='str', choices=['admin', 'monitor', 'unpriv', 'v_admin']),
)
element_spec = dict(
users=dict(type='list', elements='dict', options=user_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(module_params)
self.validate_param_values(self._required_config)
def _set_snmp_config(self, users_config):
if users_config[0]:
if users_config[0].get('Lines'):
return
current_users = []
count = 0
enabled = True
set_access_enabled = True
require_privacy = True
auth_type = ''
capability_level = ''
name = ''
all_users_names = []
for user in users_config:
user_dict = {}
entry_dict = {}
for entry in user:
name = entry.split()[2]
if user.get(entry):
if user.get(entry)[0]:
enabled = user.get(entry)[0].get('Enabled overall')
if enabled == 'no':
enabled = False
else:
enabled = True
set_access_enabled = user.get(entry)[1].get('SET access')[0].get('Enabled')
if set_access_enabled == 'no':
set_access_enabled = False
else:
set_access_enabled = True
require_privacy = user.get(entry)[0].get('Require privacy')
if require_privacy == 'yes':
require_privacy = True
else:
require_privacy = False
capability_level = user.get(entry)[1].get('SET access')[0].get('Capability level')
auth_type = user.get(entry)[0].get('Authentication type')
user_dict['enabled'] = enabled
user_dict['set_access_enabled'] = set_access_enabled
user_dict['auth_type'] = auth_type
user_dict['capability_level'] = capability_level
user_dict['require_privacy'] = require_privacy
entry_dict[name] = user_dict
all_users_names.append(name)
current_users.append(entry_dict)
self._current_config['users'] = current_users
self._current_config['current_names'] = all_users_names
def _show_users(self):
cmd = "show snmp user"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
users_config = self._show_users()
if users_config:
self._set_snmp_config(users_config)
def generate_commands(self):
req_uers = self._required_config.get("users")
current_users = self._current_config.get("users")
current_names = self._current_config.get("current_names")
if req_uers:
for user in req_uers:
user_id = user.get('name')
if user_id:
if current_names and (user_id in current_names):
for user_entry in current_users:
for user_name in user_entry:
if user_name == user_id:
user_state = user.get("enabled")
user_entry_name = user_entry.get(user_name)
if user_state is not None:
if user_state != user_entry_name.get("enabled"):
if user_state is True:
self._commands.append('snmp-server user {0} v3 enable' .format(user_id))
else:
self._commands.append('no snmp-server user {0} v3 enable' .format(user_id))
set_state = user.get("set_access_enabled")
if set_state is not None:
if set_state != user_entry_name.get("set_access_enabled"):
if set_state is True:
self._commands.append('snmp-server user {0} v3 enable sets' .format(user_id))
else:
self._commands.append('no snmp-server user {0} v3 enable sets' .format(user_id))
auth_type = user.get("auth_type")
if auth_type is not None:
if user.get("auth_password") is not None:
if auth_type != user_entry_name.get("auth_type"):
self._commands.append('snmp-server user {0} v3 auth {1} {2}'
.format(user_id, user.get('auth_type'), user.get('auth_password')))
cap_level = user.get("capability_level")
if cap_level is not None:
if cap_level != user_entry_name.get("capability_level"):
self._commands.append('snmp-server user {0} v3 capability {1}'
.format(user_id, user.get('capability_level')))
req_priv = user.get("require_privacy")
if req_priv is not None:
if req_priv != user_entry_name.get("require_privacy"):
if req_priv is True:
self._commands.append('snmp-server user {0} v3 require-privacy' .format(user_id))
else:
self._commands.append('no snmp-server user {0} v3 require-privacy' .format(user_id))
else:
user_state = user.get("enabled")
if user_state is not None:
if user_state is True:
self._commands.append('snmp-server user {0} v3 enable' .format(user_id))
else:
self._commands.append('no snmp-server user {0} v3 enable' .format(user_id))
set_state = user.get("set_access_enabled")
if set_state is not None:
if set_state is True:
self._commands.append('snmp-server user {0} v3 enable sets' .format(user_id))
else:
self._commands.append('no snmp-server user {0} v3 enable sets' .format(user_id))
if user.get("capability_level") is not None:
self._commands.append('snmp-server user {0} v3 capability {1}' .format(user_id, user.get('capability_level')))
req_priv = user.get("require_privacy")
if req_priv is not None:
if req_priv is True:
self._commands.append('snmp-server user {0} v3 require-privacy' .format(user_id))
else:
self._commands.append('no snmp-server user {0} v3 require-privacy' .format(user_id))
def main():
""" main entry point for module execution
"""
OnyxSNMPUsersModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,248 @@
#!/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
DOCUMENTATION = '''
module: onyx_syslog_files
version_added: '0.2.0'
author: "Anas Shami (@anass)"
short_description: Configure file management syslog module
description:
- This module provides declarative management of syslog
on Mellanox ONYX network devices.
notes:
options:
debug:
description:
- Configure settings for debug log files
type: bool
default: False
delete_group:
description:
- Delete certain log files
choices: ['current', 'oldest']
type: str
rotation:
description:
- rotation related attributes
type: dict
suboptions:
frequency:
description:
- Rotate log files on a fixed time-based schedule
choices: ['daily', 'weekly', 'monthly']
type: str
force:
description:
- force an immediate rotation of log files
type: bool
max_num:
description:
- Sepcify max_num of old log files to keep
type: int
size:
description:
- Rotate files when they pass max size
type: float
size_pct:
description:
- Rotatoe files when they pass percent of HD
type: float
upload_url:
description:
- upload local log files to remote host (ftp, scp, sftp, tftp) with format protocol://username[:password]@server/path
type: str
upload_file:
description:
- Upload compressed log file (current or filename)
type: str
'''
EXAMPLES = """
- name: Syslog delete old files
- onyx_syslog_files:
delete_group: oldest
- name: Syslog upload file
- onyx_syslog_files:
upload_url: scp://username:password@hostnamepath/filename
upload_file: current
- name: Syslog rotation force, frequency and max number
- onyx_syslog_files:
rotation:
force: true
max_num: 30
frequency: daily
size: 128
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- logging files delete current
- logging files rotate criteria
- logging files upload current url
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxSyslogFilesModule(BaseOnyxModule):
MAX_FILES = 999999
URL_REGEX = re.compile(
r'^(ftp|scp|ftps):\/\/[a-z0-9\.]*:(.*)@(.*):([a-zA-Z\/\/])*$')
FREQUANCIES = ['daily', 'weekly', 'monthly']
ROTATION_KEYS = ['frequency', 'max_num', 'size', 'size_pct', 'force']
ROTATION_CMDS = {'size': 'logging {0} rotation criteria size {1}',
'frequency': 'logging {0} rotation criteria frequency {1}',
'max_num': 'logging {0} rotation max-num {1}',
'size_pct': 'logging {0} rotation criteria size-pct {1}',
'force': 'logging {0} rotation force'}
def init_module(self):
"""" Ansible module initialization
"""
rotation_spec = dict(frequency=dict(choices=self.FREQUANCIES),
max_num=dict(type="int"),
force=dict(type="bool"),
size=dict(type="float"),
size_pct=dict(type="float"))
element_spec = dict(delete_group=dict(choices=['oldest', 'current']),
rotation=dict(type="dict", options=rotation_spec),
upload_file=dict(type="str"),
upload_url=dict(type="str"),
debug=dict(type="bool", default=False))
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_together=[['upload_file', 'upload_url']])
def validate_rotation(self, rotation):
size_pct = rotation.get('size_pct', None)
max_num = rotation.get('max_num', None)
if size_pct is not None and (float(size_pct) < 0 or float(size_pct) > 100):
self._module.fail_json(
msg='logging size_pct must be in range 0-100')
elif max_num is not None and (int(max_num) < 0 or int(max_num) > self.MAX_FILES):
self._module.fail_json(
msg='logging max_num must be positive number less than {0}'.format(self.MAX_FILES))
def validate_upload_url(self, upload_url):
check = self.URL_REGEX.match(upload_url)
if upload_url and not check:
self._module.fail_json(
msg='Invalid url, make sure that you use "[ftp, scp, tftp, sftp]://username:password@hostname:/location" format')
def show_logging(self):
show_logging = show_cmd(self._module, "show logging", json_fmt=True, fail_on_error=False)
running_config = show_cmd(self._module, "show running-config | include .*logging.*debug-files.*", json_fmt=True, fail_on_error=False)
if len(show_logging) > 0:
show_logging[0]['debug'] = running_config['Lines'] if 'Lines' in running_config else []
else:
show_logging = [{
'debug': running_config['Lines'] if 'Lines' in running_config else []
}]
return show_logging
def load_current_config(self):
self._current_config = dict()
current_config = self.show_logging()[0]
freq = current_config.get('Log rotation frequency') # daily (Once per day at midnight)
size = current_config.get('Log rotation size threshold') # 19.07 megabytes or 10.000% of partition (987.84 megabytes)
max_num = current_config.get('Number of archived log files to keep')
if freq is not None:
freq_str = freq.split()[0]
self._current_config['frequency'] = freq_str
if size is not None:
size_arr = size.split(' ')
if '%' in size:
size_pct_value = size_arr[0].replace('%', '')
self._current_config['size_pct'] = float(size_pct_value)
size_value = re.sub(r'(\(|\)|megabytes)', '', size_arr[-2]).strip()
self._current_config['size'] = float(size_value)
else:
size_value = size_arr[0]
self._current_config['size'] = float(size_value)
if max_num is not None:
self._current_config['max_num'] = int(max_num)
'''debug params'''
for line in current_config['debug']:
if 'size' in line:
self._current_config['debug_size'] = float(line.split(' ')[-1])
elif 'frequency' in line:
self._current_config['debug_frequency'] = line.split(' ')[-1]
elif 'size-pct' in line:
self._current_config['debug_size_pct'] = float(line.split(' ')[-1])
elif 'max-num' in line:
self._current_config['debug_max_num'] = int(line.split(' ')[-1])
def get_required_config(self):
self._required_config = dict()
required_config = dict()
module_params = self._module.params
delete_group = module_params.get('delete_group')
upload_file = module_params.get('upload_file')
rotation = module_params.get('rotation')
if delete_group:
required_config['delete_group'] = delete_group
if upload_file:
required_config.update({'upload_file': upload_file,
'upload_url': module_params.get('upload_url')})
if rotation:
required_config['rotation'] = rotation
required_config['debug'] = module_params['debug']
self.validate_param_values(required_config)
self._required_config = required_config
def generate_commands(self):
required_config = self._required_config
current_config = self._current_config
logging_files_type = 'debug-files' if required_config['debug'] else 'files'
debug_prefix = 'debug_' if required_config['debug'] else ''
rotation = required_config.get('rotation')
if rotation:
for key in rotation:
if rotation.get(key) and current_config.get(debug_prefix + key) != rotation.get(key):
cmd = self.ROTATION_CMDS[key].format(logging_files_type, rotation[key]) if key != 'force' else\
self.ROTATION_CMDS[key].format(logging_files_type)
self._commands.append(cmd)
delete_group = required_config.get('delete_group')
if delete_group:
self._commands.append('logging {0} delete {1}'.format(logging_files_type,
delete_group))
upload_file = required_config.get('upload_file')
if upload_file:
self._commands.append('logging {0} upload {1} {2}'.format(logging_files_type,
upload_file, required_config.get('upload_url')))
def main():
""" main entry point for module execution
"""
OnyxSyslogFilesModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,346 @@
#!/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
DOCUMENTATION = '''
module: onyx_syslog_remote
version_added: '0.2.0'
author: "Anas Shami (@anass)"
short_description: Configure remote syslog module
description:
- This module provides declarative management of syslog
on Mellanox ONYX network devices.
notes:
options:
enabled:
description:
- Disable/Enable logging to given remote host
default: true
type: bool
host:
description:
- <IP4/IP6 Hostname> Send event logs to this server using the syslog protocol
required: true
type: str
port:
description:
- Set remote server destination port for log messages
type: int
trap:
description:
- Minimum severity level for messages to this syslog server
choices: ['none', 'debug', 'info', 'notice', 'alert', 'warning', 'err', 'emerg', 'crit']
type: str
trap_override:
description:
- Override log levels for this sink on a per-class basis
type: list
suboptions:
override_class:
description:
- Specify a class whose log level to override
choices: ['mgmt-front', 'mgmt-back', 'mgmt-core', 'events', 'debug-module', 'sx-sdk', 'mlx-daemons', 'protocol-stack']
required: True
type: str
override_priority:
description:
-Specify a priority whose log level to override
choices: ['none', 'debug', 'info', 'notice', 'alert', 'warning', 'err', 'emerg', 'crit']
type: str
override_enabled:
description:
- disable override priorities for specific class.
default: True
type: bool
filter:
description:
- Specify a filter type
choices: ['include', 'exclude']
type: str
filter_str:
description:
- Specify a regex filter string
type: str
'''
EXAMPLES = """
- name: Remote logging port 8080
- onyx_syslog_remote:
host: 10.10.10.10
port: 8080
- name: Remote logging trap override
- onyx_syslog_remote:
host: 10.10.10.10
trap_override:
- override_class: events
override_priority: emerg
- name: Remote logging trap emerg
- onyx_syslog_remote:
host: 10.10.10.10
trap: emerg
- name: Remote logging filter include 'ERR'
- onyx_syslog_remote:
host: 10.10.10.10
filter: include
filter_str: /ERR/
- name: Disable remote logging with class events
- onyx_syslog_remote:
enabled: False
host: 10.10.10.10
class: events
- name : disable remote logging
- onyx_syslog_remote:
enabled: False
host: 10.10.10.10
- name : enable/disable override class
- onyx_syslog_remote:
host: 10.7.144.71
trap_override:
- override_class: events
override_priority: emerg
override_enabled: False
- override_class: mgmt-front
override_priority: alert
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- logging x port 8080
- logging 10.10.10.10 trap override class events priority emerg
- no logging 10.10.10.10 trap override class events
- logging 10.10.10.10 trap emerg
- logging 10.10.10.10 filter [include | exclude] ERR
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxSyslogRemoteModule(BaseOnyxModule):
MAX_PORT = 65535
LEVELS = ['none', 'debug', 'info', 'notice', 'alert', 'warning', 'err', 'emerg', 'crit']
CLASSES = ['mgmt-front', 'mgmt-back', 'mgmt-core', 'events', 'debug-module', 'sx-sdk', 'mlx-daemons', 'protocol-stack']
FILTER = ['include', 'exclude']
LOGGING_HOST = re.compile(r'^logging ([a-z0-9\.]+)$')
LOGGING_PORT = re.compile(r'^logging ([a-z0-9\.]+) port ([0-9]+)$')
LOGGING_TRAP = re.compile(r'^logging ([a-z0-9\.]+) trap ([a-z]+)$')
LOGGING_TRAP_OVERRIDE = re.compile(r'^logging ([a-z0-9\.]+) trap override class ([a-z\-]+) priority ([a-z]+)$')
LOGGING_FILTER = re.compile(r'^logging ([a-z0-9\.]+) filter (include|exclude) "([\D\d]+)"$')
def init_module(self):
"""" Ansible module initialization
"""
override_spec = dict(override_priority=dict(choices=self.LEVELS),
override_class=dict(choices=self.CLASSES, required=True),
override_enabled=dict(default=True, type="bool"))
element_spec = dict(enabled=dict(type="bool", default=True),
host=dict(type="str", required=True),
port=dict(type="int"),
trap=dict(choices=self.LEVELS),
trap_override=dict(type="list", elements='dict', options=override_spec),
filter=dict(choices=self.FILTER),
filter_str=dict(type="str"))
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_together=[
['filter', 'filter_str']
])
def validate_port(self, port):
if port and (port < 1 or port > self.MAX_PORT):
self._module.fail_json(msg='logging port must be between 1 and {0}'.format(self.MAX_PORT))
def show_logging(self):
# we can't use show logging it has lack of information
return show_cmd(self._module, "show running-config | include .*logging.*", json_fmt=False, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
current_config = self.show_logging().split('\n')
for line in current_config:
line = line.strip()
match = self.LOGGING_HOST.match(line)
if match:
host = match.group(1)
self._current_config[host] = dict()
continue
match = self.LOGGING_PORT.match(line)
if match:
host = match.group(1)
port = int(match.group(2))
if host in self._current_config:
self._current_config[host]['port'] = port
else:
self._current_config[host] = dict(port=port)
continue
match = self.LOGGING_TRAP.match(line)
if match:
host = match.group(1)
trap = match.group(2)
host_config = self._current_config.get(host)
if host_config:
self._current_config[host]['trap'] = trap
else:
self._current_config[host] = dict(trap=trap)
continue
match = self.LOGGING_TRAP_OVERRIDE.match(line)
if match:
host = match.group(1)
override_class = match.group(2)
override_priority = match.group(3)
host_config = self._current_config.get(host)
if host_config:
if 'trap_override' in host_config:
self._current_config[host]['trap_override'].append(dict(override_class=override_class, override_priority=override_priority))
else:
self._current_config[host]['trap_override'] = [dict(override_class=override_class, override_priority=override_priority)]
else:
self._current_config[host] = {'trap_override': [dict(override_class=override_class, override_priority=override_priority)]}
continue
match = self.LOGGING_FILTER.match(line)
if match:
host = match.group(1)
filter_type = match.group(2)
filter_str = match.group(3)
if host in self._current_config:
self._current_config[host].update({'filter': filter_type, 'filter_str': filter_str})
else:
self._current_config[host] = dict(filter=filter_type, filter_str=filter_str)
def get_required_config(self):
self._required_config = dict()
required_config = dict()
module_params = self._module.params
port = module_params.get('port')
trap = module_params.get('trap')
trap_override = module_params.get('trap_override')
filtered = module_params.get('filter')
required_config['host'] = module_params.get('host')
required_config['enabled'] = module_params.get('enabled')
if port:
required_config['port'] = port
if trap:
required_config['trap'] = trap
if trap_override:
required_config['trap_override'] = trap_override
if filtered:
required_config['filter'] = filtered
required_config['filter_str'] = module_params.get('filter_str', '')
self.validate_param_values(required_config)
self._required_config = required_config
def generate_commands(self):
required_config = self._required_config
current_config = self._current_config
host = required_config.get('host')
enabled = required_config['enabled']
'''
cases:
if host in current config and current config != required config and its enable
if host in current config and its disable
if host in current and it has override_class with disable flag
'''
host_config = current_config.get(host, dict())
if host in current_config and not enabled:
self._commands.append('no logging {0}'.format(host))
else:
if host not in current_config:
self._commands.append('logging {0}'.format(host))
if 'port' in required_config:
if required_config['port'] != host_config.get('port', None) or not host_config:
'''Edit/Create new one'''
self._commands.append('logging {0} port {1}'.format(host, required_config['port']))
if 'trap' in required_config or 'trap_override' in required_config:
trap_commands = self._get_trap_commands(host)
self._commands += trap_commands
if 'filter' in required_config:
is_change = host_config.get('filter', None) != required_config['filter'] or \
host_config.get('filter_str', None) != required_config['filter_str']
if is_change or not host_config:
self._commands.append('logging {0} filter {1} {2}'.format(host, required_config['filter'], required_config['filter_str']))
''' ********** private methods ********** '''
def _get_trap_commands(self, host):
current_config = self._current_config
required_config = self._required_config
trap_commands = []
host_config = current_config.get(host, dict())
override_list = required_config.get('trap_override')
if override_list:
current_override_list = host_config.get('trap_override', [])
for override_trap in override_list:
override_class = override_trap.get('override_class')
override_priority = override_trap.get('override_priority')
override_enabled = override_trap.get('override_enabled')
found, found_class = False, False
for current_override in current_override_list:
if current_override.get('override_class') == override_class:
found_class = True
if not override_enabled:
break
if override_priority and current_override.get('override_priority') == override_priority:
found = True
break
if override_enabled:
if not found and override_priority:
trap_commands.append('logging {0} trap override class {1} priority {2}'.format(
host, override_class, override_priority))
elif found_class: # disabled option will use only class
trap_commands.append('no logging {0} trap override class {1}'.format(
host, override_class))
else:
if required_config['enabled']: # no disabled option for this, just override trap level can be disabled
trap = required_config.get('trap')
if trap and (trap != host_config.get('trap', None) or not host_config):
trap_commands.append('logging {0} trap {1}'.format(
host, trap))
'''no disable for trap'''
return trap_commands
def main():
""" main entry point for module execution
"""
OnyxSyslogRemoteModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,321 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_traffic_class
author: "Anas Badaha (@anasb)"
short_description: Configures Traffic Class
description:
- This module provides declarative management of Traffic Class configuration
on Mellanox ONYX network devices.
options:
state:
description:
- enable congestion control on interface.
choices: ['enabled', 'disabled']
default: enabled
interfaces:
description:
- list of interfaces name.
required: true
tc:
description:
- traffic class, range 0-7.
required: true
congestion_control:
description:
- configure congestion control on interface.
suboptions:
control:
description:
- congestion control type.
choices: ['red', 'ecn', 'both']
required: true
threshold_mode:
description:
- congestion control threshold mode.
choices: ['absolute', 'relative']
required: true
min_threshold:
description:
- Set minimum-threshold value (in KBs) for marking traffic-class queue.
required: true
max_threshold:
description:
- Set maximum-threshold value (in KBs) for marking traffic-class queue.
required: true
dcb:
description:
- configure dcb control on interface.
suboptions:
mode:
description:
- dcb control mode.
choices: ['strict', 'wrr']
required: true
weight:
description:
- Relevant only for wrr mode.
'''
EXAMPLES = """
- name: Configure traffic class
onyx_traffic_class:
interfaces:
- Eth1/1
- Eth1/2
tc: 3
congestion_control:
control: ecn
threshold_mode: absolute
min_threshold: 500
max_threshold: 1500
dcb:
mode: strict
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/15 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500
- interface ethernet 1/16 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500
- interface mlag-port-channel 7 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500
- interface port-channel 1 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500
- interface ethernet 1/15 traffic-class 3 dcb ets strict
- interface ethernet 1/16 traffic-class 3 dcb ets strict
- interface mlag-port-channel 7 traffic-class 3 dcb ets strict
- interface port-channel 1 traffic-class 3 dcb ets strict
"""
import re
from ansible.module_utils.six import iteritems
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxTrafficClassModule(BaseOnyxModule):
IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$")
IF_PO_REGEX = re.compile(r"^Po(\d+)$")
MLAG_NAME_REGEX = re.compile(r"^Mpo(\d+)$")
IF_TYPE_ETH = "ethernet"
PORT_CHANNEL = "port-channel"
MLAG_PORT_CHANNEL = "mlag-port-channel"
IF_TYPE_MAP = {
IF_TYPE_ETH: IF_ETH_REGEX,
PORT_CHANNEL: IF_PO_REGEX,
MLAG_PORT_CHANNEL: MLAG_NAME_REGEX
}
def init_module(self):
""" initialize module
"""
congestion_control_spec = dict(control=dict(choices=['red', 'ecn', 'both'], required=True),
threshold_mode=dict(choices=['absolute', 'relative'], required=True),
min_threshold=dict(type=int, required=True),
max_threshold=dict(type=int, required=True))
dcb_spec = dict(mode=dict(choices=['strict', 'wrr'], required=True),
weight=dict(type=int))
element_spec = dict(
interfaces=dict(type='list', required=True),
tc=dict(type=int, required=True),
congestion_control=dict(type='dict', options=congestion_control_spec),
dcb=dict(type='dict', options=dcb_spec),
state=dict(choices=['enabled', 'disabled'], default='enabled'))
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(module_params)
self.validate_param_values(self._required_config)
def validate_tc(self, value):
if value and not 0 <= int(value) <= 7:
self._module.fail_json(msg='tc value must be between 0 and 7')
def validate_param_values(self, obj, param=None):
dcb = obj.get("dcb")
if dcb is not None:
dcb_mode = dcb.get("mode")
weight = dcb.get("weight")
if dcb_mode == "wrr" and weight is None:
self._module.fail_json(msg='User should send weight attribute when dcb mode is wrr')
super(OnyxTrafficClassModule, self).validate_param_values(obj, param)
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_interface_congestion_control_config(self, interface_congestion_control_config,
interface, if_type, if_id):
tc = self._required_config.get("tc")
interface_dcb_ets = self._show_interface_dcb_ets(if_type, if_id)[0].get(interface)
if interface_dcb_ets is None:
dcb = dict()
else:
ets_per_tc = interface_dcb_ets[2].get("ETS per TC")
tc_config = ets_per_tc[0].get(str(tc))
dcb_mode = tc_config[0].get("S.Mode")
dcb_weight = int(tc_config[0].get("W"))
dcb = dict(mode=dcb_mode.lower(), weight=dcb_weight)
interface_congestion_control_config = interface_congestion_control_config[tc + 1]
mode = interface_congestion_control_config.get("Mode")
if mode == "none":
self._current_config[interface] = dict(state="disabled", dcb=dcb, if_type=if_type, if_id=if_id)
return
threshold_mode = interface_congestion_control_config.get("Threshold mode")
max_threshold = interface_congestion_control_config.get("Maximum threshold")
min_threshold = interface_congestion_control_config.get("Minimum threshold")
if threshold_mode == "absolute":
delimiter = ' '
else:
delimiter = '%'
min_value = int(min_threshold.split(delimiter)[0])
max_malue = int(max_threshold.split(delimiter)[0])
congestion_control = dict(control=mode.lower(), threshold_mode=threshold_mode,
min_threshold=min_value, max_threshold=max_malue)
self._current_config[interface] = dict(state="enabled", congestion_control=congestion_control,
dcb=dcb, if_type=if_type, if_id=if_id)
def _show_interface_congestion_control(self, if_type, interface):
cmd = "show interfaces {0} {1} congestion-control".format(if_type, interface)
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _show_interface_dcb_ets(self, if_type, interface):
cmd = "show dcb ets interface {0} {1}".format(if_type, interface)
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
for interface in self._required_config.get("interfaces"):
if_type, if_id = self._get_interface_type(interface)
if not if_id:
self._module.fail_json(
msg='unsupported interface: {0}'.format(interface))
interface_congestion_control_config = self._show_interface_congestion_control(if_type, if_id)
if interface_congestion_control_config is not None:
self._set_interface_congestion_control_config(interface_congestion_control_config,
interface, if_type, if_id)
else:
self._module.fail_json(
msg='Interface {0} does not exist on switch'.format(interface))
def generate_commands(self):
state = self._required_config.get("state")
tc = self._required_config.get("tc")
interfaces = self._required_config.get("interfaces")
for interface in interfaces:
current_interface = self._current_config.get(interface)
current_state = current_interface.get("state")
if_type = current_interface.get("if_type")
if_id = current_interface.get("if_id")
if state == "disabled":
if current_state == "enabled":
self._commands.append('interface {0} {1} no traffic-class {2} congestion-control'.format(if_type, if_id, tc))
continue
congestion_control = self._required_config.get("congestion_control")
if congestion_control is not None:
control = congestion_control.get("control")
current_congestion_control = current_interface.get("congestion_control")
threshold_mode = congestion_control.get("threshold_mode")
min_threshold = congestion_control.get("min_threshold")
max_threshold = congestion_control.get("max_threshold")
if current_congestion_control is None:
self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc,
control, min_threshold, max_threshold)
else:
current_control = current_congestion_control.get("control")
curr_threshold_mode = current_congestion_control.get("threshold_mode")
curr_min_threshold = current_congestion_control.get("min_threshold")
curr_max_threshold = current_congestion_control.get("max_threshold")
if control != current_control:
self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc,
control, min_threshold, max_threshold)
else:
if threshold_mode != curr_threshold_mode:
self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc,
control, min_threshold, max_threshold)
elif min_threshold != curr_min_threshold or max_threshold != curr_max_threshold:
self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc,
control, min_threshold, max_threshold)
dcb = self._required_config.get("dcb")
if dcb is not None:
dcb_mode = dcb.get("mode")
current_dcb = current_interface.get("dcb")
current_dcb_mode = current_dcb.get("mode")
if dcb_mode == "strict" and dcb_mode != current_dcb_mode:
self._commands.append('interface {0} {1} traffic-class {2} '
'dcb ets {3}'.format(if_type, if_id, tc, dcb_mode))
elif dcb_mode == "wrr":
weight = dcb.get("weight")
current_weight = current_dcb.get("weight")
if dcb_mode != current_dcb_mode or weight != current_weight:
self._commands.append('interface {0} {1} traffic-class {2} '
'dcb ets {3} {4}'.format(if_type, if_id, tc, dcb_mode, weight))
def _threshold_mode_generate_cmds_mappers(self, threshold_mode, if_type, if_id, tc,
control, min_threshold, max_threshold):
if threshold_mode == 'absolute':
self._generate_congestion_control_absolute_cmds(if_type, if_id, tc, control,
min_threshold, max_threshold)
else:
self._generate_congestion_control_relative_cmds(if_type, if_id, tc, control,
min_threshold, max_threshold)
def _generate_congestion_control_absolute_cmds(self, if_type, if_id, tc, control,
min_absolute, max_absolute):
self._commands.append('interface {0} {1} traffic-class {2} '
'congestion-control {3} minimum-absolute {4} '
'maximum-absolute {5}'.format(if_type, if_id, tc, control,
min_absolute, max_absolute))
def _generate_congestion_control_relative_cmds(self, if_type, if_id, tc, control,
min_relative, max_relative):
self._commands.append('interface {0} {1} traffic-class {2} '
'congestion-control {3} minimum-relative {4} '
'maximum-relative {5}'.format(if_type, if_id, tc, control,
min_relative, max_relative))
def main():
""" main entry point for module execution
"""
OnyxTrafficClassModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,286 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_username
version_added: '0.2.0'
author: "Anas Shami (@anass)"
short_description: Configure username module
description:
- This module provides declarative management of users/roles
on Mellanox ONYX network devices.
notes:
options:
username:
description:
- Create/Edit user using username
type: str
required: True
full_name:
description:
- Set the full name of this user
type: str
nopassword:
description:
- Clear password for such user
type: bool
default: False
password:
description:
- Set password fot such user
type: str
encrypted_password:
description:
- Decide the type of setted password (plain text or encrypted)
type: bool
default: False
capability:
description:
- Grant capability to this user account
type: str
choices: ['monitor', 'unpriv', 'v_admin', 'admin']
reset_capability:
description:
- Reset capability to this user account
type: bool
default: False
disconnected:
description:
- Disconnect all sessions of this user
type: bool
default: False
disabled:
description:
- Disable means of logging into this account
type: str
choices: ['none', 'login', 'password', 'all']
state:
description:
- Set state of the given account
default: present
type: str
choices: ['present', 'absent']
'''
EXAMPLES = """
- name: Create new user
onyx_username:
username: anass
- name: Set the user full-name
onyx_username:
username: anass
full_name: anasshami
- name: Set the user encrypted password
onyx_username:
username: anass
password: 12345
encrypted_password: True
- name: Set the user capability
onyx_username:
username: anass
capability: monitor
- name: Reset the user capability
onyx_username:
username: anass
reset_capability: True
- name: Remove the user configuration
onyx_username:
username: anass
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- username *
- username * password *
- username * nopassword
- username * disable login
- username * capability admin
- no username *
- no username * disable
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule, show_cmd
class OnyxUsernameModule(BaseOnyxModule):
ACCOUNT_STATE = {
'Account locked out': dict(disabled='all'),
'No password required for login': dict(nopassword=True),
'Local password login disabled': dict(disabled='password'),
'Account disabled': dict(disabled='all')
}
ENCRYPTED_ID = 7
def init_module(self):
"""
module initialization
"""
element_spec = dict()
argument_spec = dict(state=dict(choices=['absent', 'present'], default='present'),
username=dict(type='str', required=True),
disabled=dict(choices=['none', 'login', 'password', 'all']),
capability=dict(choices=['monitor', 'unpriv', 'v_admin', 'admin']),
nopassword=dict(type='bool', default=False),
password=dict(type='str', no_log=True),
encrypted_password=dict(type='bool', default=False),
reset_capability=dict(type="bool", default=False),
disconnected=dict(type='bool', default=False),
full_name=dict(type='str'))
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
mutually_exclusive=[['password', 'nopassword']])
def get_required_config(self):
self._required_config = dict()
module_params = self._module.params
params = {}
''' Requred/Default fields '''
params['username'] = module_params.get('username')
params['state'] = module_params.get('state')
params['encrypted_password'] = module_params.get('encrypted_password')
params['reset_capability'] = module_params.get('reset_capability')
''' Other fields '''
for key, value in module_params.items():
if value is not None:
params[key] = value
self.validate_param_values(params)
self._required_config = params
def _get_username_config(self):
return show_cmd(self._module, "show usernames", json_fmt=True, fail_on_error=False)
def _set_current_config(self, users_config):
'''
users_config ex:
{
admin": [
{
"CAPABILITY": "admin",
"ACCOUNT STATUS": "No password required for login",
"FULL NAME": "System Administrator"
}
],
}
'''
if not users_config:
return
current_config = self._current_config
for username, config in users_config.items():
config_json = config[0]
current_config[username] = current_config.get(username, {})
account_status = config_json.get('ACCOUNT STATUS')
status_value = self.ACCOUNT_STATE.get(account_status)
if status_value is not None:
# None for enabled account with password account "Password set (SHA512 | MD5)" so we won't change any attribute here.
current_config[username].update(status_value)
current_config[username].update({
'capability': config_json.get('CAPABILITY'),
'full_name': config_json.get('FULL NAME')
})
def load_current_config(self):
self._current_config = dict()
users_config = self._get_username_config()
self._set_current_config(users_config)
def generate_commands(self):
current_config, required_config = self._current_config, self._required_config
username = required_config.get('username')
current_user = current_config.get(username)
if current_user is not None:
'''created account we just need to edit his attributes'''
full_name = required_config.get('full_name')
if full_name is not None and current_user.get('full_name') != full_name:
self._commands.append("username {0} full-name {1}".format(username, full_name))
disabled = required_config.get('disabled')
if disabled is not None and current_user.get('disabled') != disabled:
if disabled == 'none':
self._commands.append("no username {0} disable".format(username))
elif disabled == 'all':
self._commands.append("username {0} disable".format(username))
else:
self._commands.append("username {0} disable {1}".format(username, disabled))
state = required_config.get('state')
if state == 'absent': # this will remove the user
self._commands.append("no username {0}".format(username))
capability = required_config.get('capability')
if capability is not None and current_user.get('capability') != capability:
self._commands.append("username {0} capability {1}".format(username, capability))
reset_capability = required_config.get('reset_capability')
if reset_capability:
self._commands.append("no username {0} capability".format(username))
password = required_config.get('password')
if password is not None:
encrypted = required_config.get('encrypted_password')
if encrypted:
self._commands.append("username {0} password {1} {2}".format(username, self.ENCRYPTED_ID, password))
else:
self._commands.append("username {0} password {1}".format(username, password))
nopassword = required_config.get('nopassword')
if nopassword and nopassword != current_user.get('nopassword', False):
self._commands.append("username {0} nopassword".format(username))
disconnected = required_config.get('disconnected')
if disconnected:
self._commands.append("username {0} disconnect".format(username))
else:
'''create new account if we have valid inforamtion, just check for username, capability, full_name, password'''
capability = required_config.get('capability')
password = required_config.get('password')
full_name = required_config.get('full_name')
if capability is not None or password is not None or full_name is not None:
if capability is not None:
self._commands.append("username {0} capability {1}".format(username, capability))
if password is not None:
encrypted = required_config.get('encrypted_password')
if encrypted:
self._commands.append("username {0} password {1} {2} ".format(username, self.ENCRYPTED_ID, password))
else:
self._commands.append("username {0} password {1}".format(username, password))
if full_name is not None:
self._commands.append("username {0} full-name {1}".format(username, full_name))
else:
self._commands.append("username {0}".format(username))
def main():
""" main entry point for module execution
"""
OnyxUsernameModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,200 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_vlan
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
type: bool
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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
from ansible_collections.mellanox.onyx.plugins.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):
if self._os_version >= self.ONYX_API_VERSION:
vlan_data = vlan_data[0]
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._os_version = self._get_os_version()
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()

View File

@@ -0,0 +1,260 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_vxlan
author: "Anas Badaha (@anasb)"
short_description: Configures Vxlan
description:
- This module provides declarative management of Vxlan configuration
on Mellanox ONYX network devices.
notes:
- Tested on ONYX evpn_dev.031.
- nve protocol must be enabled.
options:
nve_id:
description:
- nve interface ID.
required: true
loopback_id:
description:
- loopback interface ID.
bgp:
description:
- configure bgp on nve interface.
type: bool
default: true
mlag_tunnel_ip:
description:
- vxlan Mlag tunnel IP
vni_vlan_list:
description:
- Each item in the list has two attributes vlan_id, vni_id.
arp_suppression:
description:
- A flag telling if to configure arp suppression.
type: bool
default: false
'''
EXAMPLES = """
- name: Configure Vxlan
onyx_vxlan:
nve_id: 1
loopback_id: 1
bgp: yes
mlag-tunnel-ip: 100.0.0.1
vni_vlan_list:
- vlan_id: 10
vni_id: 10010
- vlan_id: 6
vni_id: 10060
arp_suppression: yes
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface nve 1
- interface nve 1 vxlan source interface loopback 1
- interface nve 1 nve controller bgp
- interface nve 1 vxlan mlag-tunnel-ip 100.0.0.1
- interface nve 1 nve vni 10010 vlan 10
- interface nve 1 nve vni 10060 vlan 6
- interface nve 1 nve neigh-suppression
- interface vlan 6
- interface vlan 10
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
class OnyxVxlanModule(BaseOnyxModule):
LOOPBACK_REGEX = re.compile(r'^loopback (\d+).*')
NVE_ID_REGEX = re.compile(r'^Interface NVE (\d+).*')
def init_module(self):
""" initialize module
"""
vni_vlan_spec = dict(vlan_id=dict(type=int),
vni_id=dict(type=int))
element_spec = dict(
nve_id=dict(type=int),
loopback_id=dict(type=int),
bgp=dict(default=True, type='bool'),
mlag_tunnel_ip=dict(type='str'),
vni_vlan_list=dict(type='list',
elements='dict',
options=vni_vlan_spec),
arp_suppression=dict(default=False, type='bool')
)
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(module_params)
self.validate_param_values(self._required_config)
def _set_vxlan_config(self, vxlan_config):
vxlan_config = vxlan_config[0]
if not vxlan_config:
return
nve_header = vxlan_config.get("header")
match = self.NVE_ID_REGEX.match(nve_header)
if match:
current_nve_id = int(match.group(1))
self._current_config['nve_id'] = current_nve_id
if int(current_nve_id) != self._required_config.get("nve_id"):
return
self._current_config['mlag_tunnel_ip'] = vxlan_config.get("Mlag tunnel IP")
controller_mode = vxlan_config.get("Controller mode")
if controller_mode == "BGP":
self._current_config['bgp'] = True
else:
self._current_config['bgp'] = False
loopback_str = vxlan_config.get("Source interface")
match = self.LOOPBACK_REGEX.match(loopback_str)
if match:
loopback_id = match.group(1)
self._current_config['loopback_id'] = int(loopback_id)
self._current_config['global_neigh_suppression'] = vxlan_config.get("Global Neigh-Suppression")
vni_vlan_mapping = self._current_config['vni_vlan_mapping'] = dict()
nve_detail = self._show_nve_detail()
if nve_detail is not None:
nve_detail = nve_detail[0]
if nve_detail:
for vlan_id in nve_detail:
vni_vlan_mapping[int(vlan_id)] = dict(
vni_id=int(nve_detail[vlan_id][0].get("VNI")),
arp_suppression=nve_detail[vlan_id][0].get("Neigh Suppression"))
def _show_vxlan_config(self):
cmd = "show interfaces nve"
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def _show_nve_detail(self):
cmd = "show interface nve {0} detail".format(self._required_config.get("nve_id"))
return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False)
def load_current_config(self):
self._current_config = dict()
vxlan_config = self._show_vxlan_config()
if vxlan_config:
self._set_vxlan_config(vxlan_config)
def generate_commands(self):
nve_id = self._required_config.get("nve_id")
current_nve_id = self._current_config.get("nve_id")
if current_nve_id is None:
self._add_nve_commands(nve_id)
elif current_nve_id != nve_id:
self._add_no_nve_commands(current_nve_id)
self._add_nve_commands(nve_id)
bgp = self._required_config.get("bgp")
if bgp is not None:
curr_bgp = self._current_config.get("bgp")
if bgp and bgp != curr_bgp:
self._commands.append('interface nve {0} nve controller bgp'.format(nve_id))
loopback_id = self._required_config.get("loopback_id")
if loopback_id is not None:
curr_loopback_id = self._current_config.get("loopback_id")
if loopback_id != curr_loopback_id:
self._commands.append('interface nve {0} vxlan source interface '
'loopback {1} '.format(nve_id, loopback_id))
mlag_tunnel_ip = self._required_config.get("mlag_tunnel_ip")
if mlag_tunnel_ip is not None:
curr_mlag_tunnel_ip = self._current_config.get("mlag_tunnel_ip")
if mlag_tunnel_ip != curr_mlag_tunnel_ip:
self._commands.append('interface nve {0} vxlan '
'mlag-tunnel-ip {1}'.format(nve_id, mlag_tunnel_ip))
vni_vlan_list = self._required_config.get("vni_vlan_list")
arp_suppression = self._required_config.get("arp_suppression")
if vni_vlan_list is not None:
self._generate_vni_vlan_cmds(vni_vlan_list, nve_id, arp_suppression)
def _generate_vni_vlan_cmds(self, vni_vlan_list, nve_id, arp_suppression):
current_global_arp_suppression = self._current_config.get('global_neigh_suppression')
if arp_suppression is True and current_global_arp_suppression != "Enable":
self._commands.append('interface nve {0} nve neigh-suppression'.format(nve_id))
current_vni_vlan_mapping = self._current_config.get('vni_vlan_mapping')
if current_vni_vlan_mapping is None:
for vni_vlan in vni_vlan_list:
vlan_id = vni_vlan.get("vlan_id")
vni_id = vni_vlan.get("vni_id")
self._add_vni_vlan_cmds(nve_id, vni_id, vlan_id)
self._add_arp_suppression_cmds(arp_suppression, vlan_id)
else:
for vni_vlan in vni_vlan_list:
vlan_id = vni_vlan.get("vlan_id")
vni_id = vni_vlan.get("vni_id")
currt_vlan_id = current_vni_vlan_mapping.get(vlan_id)
if currt_vlan_id is None:
self._add_vni_vlan_cmds(nve_id, vni_id, vlan_id)
self._add_arp_suppression_cmds(arp_suppression, vlan_id)
else:
current_vni_id = currt_vlan_id.get("vni_id")
current_arp_suppression = currt_vlan_id.get("arp_suppression")
if int(current_vni_id) != vni_id:
self._add_vni_vlan_cmds(nve_id, vni_id, vlan_id)
if current_arp_suppression == "Disable":
self._add_arp_suppression_cmds(arp_suppression, vlan_id)
def _add_no_nve_commands(self, current_nve_id):
self._commands.append('no interface nve {0}'.format(current_nve_id))
def _add_nve_commands(self, nve_id):
self._commands.append('interface nve {0}'.format(nve_id))
self._commands.append('exit')
def _add_vni_vlan_cmds(self, nve_id, vni_id, vlan_id):
self._commands.append('interface nve {0} nve vni {1} '
'vlan {2}'.format(nve_id, vni_id, vlan_id))
def _add_arp_suppression_cmds(self, arp_suppression, vlan_id):
if arp_suppression is True:
self._commands.append('interface vlan {0}'.format(vlan_id))
self._commands.append('exit')
def main():
""" main entry point for module execution
"""
OnyxVxlanModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,219 @@
#!/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
DOCUMENTATION = '''
---
module: onyx_wjh
author: "Anas Shami (@anass)"
short_description: Configure what-just-happend module
description:
- This module provides declarative management of wjh
on Mellanox ONYX network devices.
notes:
options:
group:
description:
- Name of wjh group.
choices: ['all', 'forwarding', 'acl']
type: str
enabled:
description:
- wjh group status
type: bool
auto_export:
description:
- wjh group auto export pcap file status
type: bool
export_group:
description:
- wjh group auto export group
choices: ['all', 'forwarding', 'acl']
type: str
clear_group:
description:
- clear pcap file by group
choices: ['all', 'user', 'auto-export']
type: str
'''
EXAMPLES = """
- name: Enable wjh
onyx_wjh:
group: forwarding
enabled: True
- name: Disable wjh
onyx_wjh:
group: forwarding
enabled: False
- name: Enable auto-export
onyx_wjh:
auto_export: True
export_group: forwarding
- name: Disable auto-export
onyx_wjh:
auto_export: False
export_group: forwarding
- name: Clear pcap file
onyx_wjh:
clear_group: auto-export
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- what-just-happend forwarding enable
- what-just-happend auto-export forwarding enable
- clear what-just-happend pcap-file user
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule, show_cmd
class OnyxWJHModule(BaseOnyxModule):
WJH_DISABLED_REGX = re.compile(r'^no what-just-happened ([a-z]+) enable.*')
WJH_DISABLED_AUTO_EXPORT_REGX = re.compile(r'^no what-just-happened auto-export ([a-z]+) enable.*')
WJH_CMD_FMT = '{0}what-just-happened {1} enable'
WJH_EXPORT_CMD_FMT = '{0}what-just-happened auto-export {1} enable'
WJH_CLEAR_CMD_FMT = 'clear what-just-happened pcap-files {0}'
WJH_GROUPS = ['all', 'forwarding', 'acl']
CLEAR_GROUPS = ['all', 'user', 'auto-export']
def init_module(self):
"""
module initialization
"""
element_spec = dict(group=dict(choices=self.WJH_GROUPS),
enabled=dict(type='bool'),
auto_export=dict(type='bool'),
export_group=dict(choices=self.WJH_GROUPS),
clear_group=dict(choices=self.CLEAR_GROUPS))
argument_spec = dict()
argument_spec.update(element_spec)
self._module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_together=[
['group', 'enabled'],
['auto_export', 'export_group']
])
def get_required_config(self):
self._required_config = dict()
module_params = self._module.params
group = module_params.get('group')
export_group = module_params.get('export_group')
clear_group = module_params.get('clear_group')
params = dict()
if group:
enabled = module_params.get('enabled')
params.update({
'group': group,
'enabled': enabled
})
if export_group:
auto_export = module_params.get('auto_export')
params.update({
'export_group': export_group,
'auto_export': auto_export
})
if clear_group:
params.update({
'clear_group': clear_group
})
self.validate_param_values(params)
self._required_config = params
def _get_wjh_config(self):
return show_cmd(self._module, "show running-config | include .*what-just-happened.*", json_fmt=False, fail_on_error=False)
def _set_current_config(self, config):
if not config:
return
current_config = self._current_config
lines = config.split('\n')
for line in lines:
if line.startswith('#'):
continue
match = self.WJH_DISABLED_REGX.match(line)
if match:
# wjh is disabled
group = match.group(1)
current_config[group] = False
match = self.WJH_DISABLED_AUTO_EXPORT_REGX.match(line)
if match:
# wjh auto export is disabled
export_group = match.group(1) + '_export'
current_config[export_group] = False
'''
show running config will contains [no wjh * group enable] if disabled - default config is enabled
'''
def load_current_config(self):
self._current_config = dict()
config_lines = self._get_wjh_config()
if config_lines:
self._set_current_config(config_lines)
def wjh_group_status(self, current_config, group_value, suffix=''):
current_enabled = False
if group_value == 'all':
# no disabled group so all would be false
current_enabled = not all([
(group + suffix) in current_config for group in self.WJH_GROUPS])
else:
# if no current-value its enabled
current_enabled = current_config[group_value + suffix] if((group_value + suffix) in current_config) else True
return current_enabled
'''
wjh is enabled "by default"
when wjh disable we will find no wjh commands in running config
'''
def generate_commands(self):
current_config, required_config = self._current_config, self._required_config
group = required_config.get('group')
export_group = required_config.get('export_group')
clear_group = required_config.get('clear_group')
if group:
current_enabled = self.wjh_group_status(current_config, group)
if(required_config['enabled'] != current_enabled):
self._commands.append(self.WJH_CMD_FMT
.format(('' if required_config['enabled'] else 'no '), group))
if export_group:
current_enabled = self.wjh_group_status(current_config, required_config['export_group'], '_export')
if(required_config['auto_export'] != current_enabled):
self._commands.append(self.WJH_EXPORT_CMD_FMT
.format(('' if required_config['auto_export'] else 'no '), export_group))
if clear_group:
# clear pcap files
self._commands.append(self.WJH_CLEAR_CMD_FMT.format(clear_group))
def main():
""" main entry point for module execution
"""
OnyxWJHModule.main()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,80 @@
#
# (c) 2016 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import re
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text, to_bytes
from ansible.plugins.terminal import TerminalBase
class TerminalModule(TerminalBase):
terminal_stdout_re = [
re.compile(br"(?P<prompt>(.*)( > | # )\Z)"),
]
terminal_stderr_re = [
re.compile(br"\A%|\r\n%|\n%"),
]
init_commands = [b'no cli session paging enable', ]
def on_open_shell(self):
try:
for cmd in self.init_commands:
self._exec_cli_command(cmd)
except AnsibleConnectionFailure:
raise AnsibleConnectionFailure('unable to set terminal parameters')
def on_become(self, passwd=None):
if self._get_prompt().endswith(b'#'):
return
cmd = {u'command': u'enable'}
if passwd:
# Note: python-3.5 cannot combine u"" and r"" together. Thus make
# an r string and use to_text to ensure it's text on both py2 and
# py3.
cmd[u'prompt'] = to_text(r"[\r\n]?password: $",
errors='surrogate_or_strict')
cmd[u'answer'] = passwd
try:
self._exec_cli_command(to_bytes(json.dumps(cmd),
errors='surrogate_or_strict'))
except AnsibleConnectionFailure:
raise AnsibleConnectionFailure(
'unable to elevate privilege to enable mode')
def on_unbecome(self):
prompt = self._get_prompt()
if prompt is None:
# if prompt is None most likely the terminal is hung up at a prompt
return
if b'(config' in prompt:
self._exec_cli_command(b'exit')
self._exec_cli_command(b'disable')
elif prompt.endswith(b'#'):
self._exec_cli_command(b'disable')