diff --git a/lib/ansible/modules/network/mlnxos/mlnxos_mlag_vip.py b/lib/ansible/modules/network/mlnxos/mlnxos_mlag_vip.py new file mode 100644 index 0000000000..3f637cc9c6 --- /dev/null +++ b/lib/ansible/modules/network/mlnxos/mlnxos_mlag_vip.py @@ -0,0 +1,179 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: mlnxos_mlag_vip +version_added: "2.5" +author: "Samer Deeb (@samerd)" +short_description: Configures MLAG VIP on Mellanox MLNX-OS network devices +description: + - This module provides declarative management of MLAG virtual IPs + on Mellanox MLNX-OS network devices. +notes: + - Tested on MLNX-OS 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 + mlnxos_mlag_vip: + ipaddress: 50.3.3.1/24 + group_name: ansible-test-group + mac_address: 00:11:12:23:34:45 +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always + type: list + sample: + - mlag-vip ansible_test_group ip 50.3.3.1 /24 force + - no mlag shutdown +""" + +import time + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.network.mlnxos.mlnxos import BaseMlnxosModule +from ansible.module_utils.network.mlnxos.mlnxos import show_cmd + + +class MlnxosMLagVipModule(BaseMlnxosModule): + + def init_module(self): + """ initialize module + """ + element_spec = dict( + ipaddress=dict(), + group_name=dict(), + mac_address=dict(), + delay=dict(type='int', default=12), + state=dict(choices=['present', 'absent'], default='present'), + ) + argument_spec = dict() + + argument_spec.update(element_spec) + self._module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True) + + def get_required_config(self): + module_params = self._module.params + lag_params = { + 'ipaddress': module_params['ipaddress'], + 'group_name': module_params['group_name'], + 'mac_address': module_params['mac_address'], + 'delay': module_params['delay'], + 'state': module_params['state'], + } + + self.validate_param_values(lag_params) + self._required_config = lag_params + + def _show_mlag_cmd(self, cmd): + return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False) + + def _show_mlag(self): + cmd = "show mlag" + return self._show_mlag_cmd(cmd) + + def _show_mlag_vip(self): + cmd = "show mlag-vip" + return self._show_mlag_cmd(cmd) + + def load_current_config(self): + self._current_config = dict() + mlag_config = self._show_mlag() + mlag_vip_config = self._show_mlag_vip() + if mlag_vip_config: + mlag_vip = mlag_vip_config.get("MLAG-VIP", {}) + self._current_config['group_name'] = \ + mlag_vip.get("MLAG group name") + self._current_config['ipaddress'] = \ + mlag_vip.get("MLAG VIP address") + if mlag_config: + self._current_config['mac_address'] = \ + mlag_config.get("System-mac") + + def generate_commands(self): + state = self._required_config['state'] + if state == 'present': + self._generate_mlag_vip_cmds() + else: + self._generate_no_mlag_vip_cmds() + + def _generate_mlag_vip_cmds(self): + current_group = self._current_config.get('group_name') + current_ip = self._current_config.get('ipaddress') + current_mac = self._current_config.get('mac_address') + if current_mac: + current_mac = current_mac.lower() + + req_group = self._required_config.get('group_name') + req_ip = self._required_config.get('ipaddress') + req_mac = self._required_config.get('mac_address') + if req_mac: + req_mac = req_mac.lower() + + if req_group != current_group or req_ip != current_ip: + ipaddr, mask = req_ip.split('/') + self._commands.append( + 'mlag-vip %s ip %s /%s force' % (req_group, ipaddr, mask)) + if req_mac != current_mac: + self._commands.append( + 'mlag system-mac %s' % (req_mac)) + if self._commands: + self._commands.append('no mlag shutdown') + + def _generate_no_mlag_vip_cmds(self): + if self._current_config.get('group_name'): + self._commands.append('no mlag-vip') + + def check_declarative_intent_params(self, result): + if not result['changed']: + return + delay_interval = self._required_config.get('delay') + if delay_interval > 0: + time.sleep(delay_interval) + for cmd in ("show mlag-vip", ""): + show_cmd(self._module, cmd, json_fmt=False, fail_on_error=False) + + +def main(): + """ main entry point for module execution + """ + MlnxosMLagVipModule.main() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/mlnxos/fixtures/mlnxos_mlag_show.cfg b/test/units/modules/network/mlnxos/fixtures/mlnxos_mlag_show.cfg new file mode 100644 index 0000000000..1996a6b31b --- /dev/null +++ b/test/units/modules/network/mlnxos/fixtures/mlnxos_mlag_show.cfg @@ -0,0 +1,18 @@ +{ + "Reload-delay": "30 sec", + "Upgrade-timeout": "60 min", + "System-mac": "00:00:5E:00:01:4E", + "Admin status": "Disabled", + "MLAG Ports Status Summary": { + "Active-partial": "0", + "Inactive": "0", + "Active-full": "0" + }, + "Keepalive-interval": "1 sec", + "MLAG Ports Configuration Summary": { + "Disabled": "0", + "Configured": "0", + "Enabled": "0" + }, + "Operational status": "Down" +} diff --git a/test/units/modules/network/mlnxos/fixtures/mlnxos_mlag_vip_show.cfg b/test/units/modules/network/mlnxos/fixtures/mlnxos_mlag_vip_show.cfg new file mode 100644 index 0000000000..19bee48f85 --- /dev/null +++ b/test/units/modules/network/mlnxos/fixtures/mlnxos_mlag_vip_show.cfg @@ -0,0 +1,19 @@ +{ + "r-neo-sw12": [ + { + "IP Address": "10.209.26.55", + "VIP-State": "standby" + } + ], + "r-smg-sw14": [ + { + "IP Address": "10.209.27.172", + "VIP-State": "master" + } + ], + "MLAG-VIP": { + "MLAG VIP address": "10.209.25.107/24", + "MLAG group name": "neo-mlag-vip-500", + "Active nodes": "2" + } +} diff --git a/test/units/modules/network/mlnxos/test_mlnxos_mlag_vip.py b/test/units/modules/network/mlnxos/test_mlnxos_mlag_vip.py new file mode 100644 index 0000000000..c0bee88fb1 --- /dev/null +++ b/test/units/modules/network/mlnxos/test_mlnxos_mlag_vip.py @@ -0,0 +1,76 @@ +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.compat.tests.mock import patch +from ansible.modules.network.mlnxos import mlnxos_mlag_vip +from units.modules.utils import set_module_args +from .mlnxos_module import TestMlnxosModule, load_fixture + + +class TestMlnxosMlagVipModule(TestMlnxosModule): + + module = mlnxos_mlag_vip + + def setUp(self): + super(TestMlnxosMlagVipModule, self).setUp() + self._mlag_enabled = True + self.mock_show_mlag = patch.object( + mlnxos_mlag_vip.MlnxosMLagVipModule, + "_show_mlag") + self.show_mlag = self.mock_show_mlag.start() + self.mock_show_mlag_vip = patch.object( + mlnxos_mlag_vip.MlnxosMLagVipModule, + "_show_mlag_vip") + self.show_mlag_vip = self.mock_show_mlag_vip.start() + + self.mock_load_config = patch( + 'ansible.module_utils.network.mlnxos.mlnxos.load_config') + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestMlnxosMlagVipModule, self).tearDown() + self.mock_show_mlag.stop() + self.mock_show_mlag_vip.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None, transport='cli'): + if self._mlag_enabled: + config_file = 'mlnxos_mlag_vip_show.cfg' + self.show_mlag_vip.return_value = load_fixture(config_file) + config_file = 'mlnxos_mlag_show.cfg' + self.show_mlag.return_value = load_fixture(config_file) + else: + self.show_mlag_vip.return_value = None + self.show_mlag.return_value = None + self.load_config.return_value = None + + def test_mlag_no_change(self): + set_module_args(dict(ipaddress='10.209.25.107/24', + group_name='neo-mlag-vip-500', + mac_address='00:00:5E:00:01:4E')) + self.execute_module(changed=False) + + def test_mlag_change(self): + self._mlag_enabled = False + set_module_args(dict(ipaddress='10.209.25.107/24', + group_name='neo-mlag-vip-500', + mac_address='00:00:5E:00:01:4E', + delay=0)) + commands = ['mlag-vip neo-mlag-vip-500 ip 10.209.25.107 /24 force', + 'mlag system-mac 00:00:5e:00:01:4e', 'no mlag shutdown'] + self.execute_module(changed=True, commands=commands) + + def test_mlag_absent_no_change(self): + self._mlag_enabled = False + set_module_args(dict(state='absent')) + self.execute_module(changed=False) + + def test_mlag_absent_change(self): + set_module_args(dict(state='absent', delay=0)) + commands = ['no mlag-vip'] + self.execute_module(changed=True, commands=commands)