diff --git a/lib/ansible/modules/network/cloudengine/ce_lacp.py b/lib/ansible/modules/network/cloudengine/ce_lacp.py
new file mode 100644
index 00000000000..d488233b81e
--- /dev/null
+++ b/lib/ansible/modules/network/cloudengine/ce_lacp.py
@@ -0,0 +1,488 @@
+#!/usr/bin/python
+#
+# 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 .
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: ce_lacp
+version_added: "2.10"
+short_description: Manages Eth-Trunk interfaces on HUAWEI CloudEngine switches
+description:
+ - Manages Eth-Trunk specific configuration parameters on HUAWEI CloudEngine switches.
+author: xuxiaowei0512 (@CloudEngine-Ansible)
+notes:
+ - C(state=absent) removes the Eth-Trunk config and interface if it
+ already exists. If members to be removed are not explicitly
+ passed, all existing members (if any), are removed,
+ and Eth-Trunk removed.
+ - Members must be a list.
+options:
+ trunk_id:
+ description:
+ - Eth-Trunk interface number.
+ The value is an integer.
+ The value range depends on the assign forward eth-trunk mode command.
+ When 256 is specified, the value ranges from 0 to 255.
+ When 512 is specified, the value ranges from 0 to 511.
+ When 1024 is specified, the value ranges from 0 to 1023.
+ type: int
+ mode:
+ description:
+ - Specifies the working mode of an Eth-Trunk interface.
+ default: null
+ choices: ['Manual','Dynamic','Static']
+ type: str
+ preempt_enable:
+ description:
+ - Specifies lacp preempt enable of Eth-Trunk lacp.
+ The value is an boolean 'true' or 'false'.
+ type: bool
+ state_flapping:
+ description:
+ - Lacp dampening state-flapping.
+ type: bool
+ port_id_extension_enable:
+ description:
+ - Enable the function of extending the LACP negotiation port number.
+ type: bool
+ unexpected_mac_disable:
+ description:
+ - Lacp dampening unexpected-mac disable.
+ type: bool
+ system_id:
+ description:
+ - Link Aggregation Control Protocol System ID,interface Eth-Trunk View.
+ - Formate 'X-X-X',X is hex(a,aa,aaa, or aaaa)
+ type: str
+ timeout_type:
+ description:
+ - Lacp timeout type,that may be 'Fast' or 'Slow'.
+ choices: ['Slow', 'Fast']
+ type: str
+ fast_timeout:
+ description:
+ - When lacp timeout type is 'Fast', user-defined time can be a number(3~90).
+ type: int
+ mixed_rate_link_enable:
+ description:
+ - Value of max active linknumber.
+ type: bool
+ preempt_delay:
+ description:
+ - Value of preemption delay time.
+ type: int
+ collector_delay:
+ description:
+ - Value of delay time in units of 10 microseconds.
+ type: int
+ max_active_linknumber:
+ description:
+ - Max active linknumber in link aggregation group.
+ type: int
+ select:
+ description:
+ - Select priority or speed to preempt.
+ choices: ['Speed', 'Prority']
+ type: str
+ priority:
+ description:
+ - The priority of eth-trunk member interface.
+ type: int
+ global_priority:
+ description:
+ - Configure lacp priority on system-view.
+ type: int
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices: ['present','absent']
+ type: str
+'''
+EXAMPLES = r'''
+ - name: Ensure Eth-Trunk100 is created, and set to mode lacp-static
+ ce_lacp:
+ trunk_id: 100
+ mode: 'lacp-static'
+ state: present
+ - name: Ensure Eth-Trunk100 is created, add two members, and set global priority to 1231
+ ce_lacp:
+ trunk_id: 100
+ global_priority: 1231
+ state: present
+ - name: Ensure Eth-Trunk100 is created, and set mode to Dynamic and configure other options
+ ce_lacp:
+ trunk_id: 100
+ mode: Dynamic
+ preempt_enable: True,
+ state_flapping: True,
+ port_id_extension_enable: True,
+ unexpected_mac_disable: True,
+ timeout_type: Fast,
+ fast_timeout: 123,
+ mixed_rate_link_enable: True,
+ preempt_delay: 23,
+ collector_delay: 33,
+ state: present
+'''
+
+RETURN = r'''
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"trunk_id": "100", "members": ['10GE1/0/24','10GE1/0/25'], "mode": "lacp-static"}
+existing:
+ description: k/v pairs of existing Eth-Trunk
+ returned: always
+ type: dict
+ sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [
+ {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}],
+ "min_links": "1", "mode": "manual"}
+end_state:
+ description: k/v pairs of Eth-Trunk info after module execution
+ returned: always
+ type: dict
+ sample: {"trunk_id": "100", "hash_type": "mac", "members_detail": [
+ {"memberIfName": "10GE1/0/24", "memberIfState": "Down"},
+ {"memberIfName": "10GE1/0/25", "memberIfState": "Down"}],
+ "min_links": "1", "mode": "lacp-static"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["interface Eth-Trunk 100",
+ "mode lacp-static",
+ "interface 10GE1/0/25",
+ "eth-trunk 100"]
+'''
+
+import xml.etree.ElementTree as ET
+import re
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config
+
+LACP = {'trunk_id': 'ifName',
+ 'mode': 'workMode',
+ 'preempt_enable': 'isSupportPrmpt',
+ 'state_flapping': 'dampStaFlapEn',
+ 'port_id_extension_enable': 'trunkPortIdExt',
+ 'unexpected_mac_disable': 'dampUnexpMacEn',
+ 'system_id': 'trunkSysMac',
+ 'timeout_type': 'rcvTimeoutType',
+ 'fast_timeout': 'fastTimeoutUserDefinedValue',
+ 'mixed_rate_link_enable': 'mixRateEnable',
+ 'preempt_delay': 'promptDelay',
+ 'collector_delay': 'collectMaxDelay',
+ 'max_active_linknumber': 'maxActiveNum',
+ 'select': 'selectPortStd',
+ 'weight': 'weight',
+ 'priority': 'portPriority',
+ 'global_priority': 'priority'
+ }
+
+
+def has_element(parent, xpath):
+ """get or create a element by xpath"""
+ ele = parent.find('./' + xpath)
+ if ele is not None:
+ return ele
+ ele = parent
+ lpath = xpath.split('/')
+ for p in lpath:
+ e = parent.find('.//' + p)
+ if e is None:
+ e = ET.SubElement(ele, p)
+ ele = e
+ return ele
+
+
+def bulid_xml(kwargs, operation='get'):
+ """create a xml tree by dictionary with operation,get,merge and delete"""
+ attrib = {'xmlns': "http://www.huawei.com/netconf/vrp",
+ 'content-version': "1.0", 'format-version': "1.0"}
+
+ root = ET.Element('ifmtrunk')
+ for key in kwargs.keys():
+ if key in ('global_priority',):
+ xpath = 'lacpSysInfo'
+ elif key in ('priority',):
+ xpath = 'TrunkIfs/TrunkIf/TrunkMemberIfs/TrunkMemberIf/lacpPortInfo/lacpPort'
+ elif key in ['preempt_enable', 'timeout_type', 'fast_timeout', 'select', 'preempt_delay',
+ 'max_active_linknumber', 'collector_delay', 'mixed_rate_link_enable',
+ 'state_flapping', 'unexpected_mac_disable', 'system_id',
+ 'port_id_extension_enable']:
+ xpath = 'TrunkIfs/TrunkIf/lacpTrunk'
+ elif key in ('trunk_id', 'mode'):
+ xpath = 'TrunkIfs/TrunkIf'
+ if xpath != '':
+ parent = has_element(root, xpath)
+ element = ET.SubElement(parent, LACP[key])
+ if operation == 'merge':
+ parent.attrib = dict(operation=operation)
+ element.text = str(kwargs[key])
+ if key == 'mode':
+ element.text = str(kwargs[key])
+ if key == 'trunk_id':
+ element.text = 'Eth-Trunk' + str(kwargs[key])
+ root.attrib = attrib
+ config = ET.tostring(root)
+ if operation == 'merge' or operation == 'delete':
+ return '%s' % to_native(config)
+ return '%s' % to_native(config)
+
+
+def check_param(kwargs):
+ """check args list,the boolean or list values cloud not be checked,because they are limit by args list in main"""
+
+ for key in kwargs:
+ if kwargs[key] is None:
+ continue
+ if key == 'trunk_id':
+ value = int(kwargs[key])
+ # maximal value is 1024,although the value is limit by command 'assign forward eth-trunk mode '
+ if value < 0 or value > 1024:
+ return 'Error: Wrong Value of Eth-Trunk interface number'
+ elif key == 'system_id':
+ # X-X-X ,X is hex(4 bit)
+ if not re.match(r'[0-9a-f]{1,4}\-[0-9a-f]{1,4}\-[0-9a-f]{1,4}', kwargs[key], re.IGNORECASE):
+ return 'Error: The system-id is invalid.'
+ values = kwargs[key].split('-')
+ flag = 0
+ # all 'X' is 0,that is invalid value
+ for v in values:
+ if len(v.strip('0')) < 1:
+ flag += 1
+ if flag == 3:
+ return 'Error: The system-id is invalid.'
+ elif key == 'timeout_type':
+ # select a value from choices, choices=['Slow','Fast'],it's checked by AnsibleModule
+ pass
+ elif key == 'fast_timeout':
+ value = int(kwargs[key])
+ if value < 3 or value > 90:
+ return 'Error: Wrong Value of timeout,fast user-defined value<3-90>'
+ rtype = str(kwargs.get('timeout_type'))
+ if rtype == 'Slow':
+ return 'Error: Short timeout period for receiving packets is need,when user define the time.'
+ elif key == 'preempt_delay':
+ value = int(kwargs[key])
+ if value < 0 or value > 180:
+ return 'Error: Value of preemption delay time is from 0 to 180'
+ elif key == 'collector_delay':
+ value = int(kwargs[key])
+ if value < 0 or value > 65535:
+ return 'Error: Value of collector delay time is from 0 to 65535'
+ elif key == 'max_active_linknumber':
+ value = int(kwargs[key])
+ if value < 0 or value > 64:
+ return 'Error: Value of collector delay time is from 0 to 64'
+ elif key == 'priority' or key == 'global_priority':
+ value = int(kwargs[key])
+ if value < 0 or value > 65535:
+ return 'Error: Value of priority is from 0 to 65535'
+ return 'ok'
+
+
+def xml_to_dict(args):
+ """transfer xml string into dict """
+ rdict = dict()
+ args = re.sub(r'xmlns=\".+?\"', '', args)
+ root = ET.fromstring(args)
+ ifmtrunk = root.find('.//ifmtrunk')
+ if ifmtrunk is not None:
+ for ele in ifmtrunk.getiterator():
+ if ele.text is not None and len(ele.text.strip()) > 0:
+ rdict[ele.tag] = ele.text
+ return rdict
+
+
+def compare_config(module, kwarg_exist, kwarg_end):
+ """compare config between exist and end"""
+ dic_command = {'isSupportPrmpt': 'lacp preempt enable',
+ 'rcvTimeoutType': 'lacp timeout', # lacp timeout fast user-defined 23
+ 'fastTimeoutUserDefinedValue': 'lacp timeout user-defined',
+ 'selectPortStd': 'lacp select',
+ 'promptDelay': 'lacp preempt delay',
+ 'maxActiveNum': 'lacp max active-linknumber',
+ 'collectMaxDelay': 'lacp collector delay',
+ 'mixRateEnable': 'lacp mixed-rate link enable',
+ 'dampStaFlapEn': 'lacp dampening state-flapping',
+ 'dampUnexpMacEn': 'lacp dampening unexpected-mac disable',
+ 'trunkSysMac': 'lacp system-id',
+ 'trunkPortIdExt': 'lacp port-id-extension enable',
+ 'portPriority': 'lacp priority', # interface 10GE1/0/1
+ 'lacpMlagPriority': 'lacp m-lag priority',
+ 'lacpMlagSysId': 'lacp m-lag system-id',
+ 'priority': 'lacp priority'
+ }
+ rlist = list()
+ exist = set(kwarg_exist.keys())
+ end = set(kwarg_end.keys())
+ undo = exist - end
+ add = end - exist
+ update = end & exist
+
+ for key in undo:
+ if key in dic_command:
+ rlist.append('undo ' + dic_command[key])
+ for key in add:
+ if key in dic_command:
+ rlist.append(dic_command[key] + ' ' + kwarg_end[key])
+ for key in update:
+ if kwarg_exist[key] != kwarg_end[key] and key in dic_command:
+ if kwarg_exist[key] == 'true' and kwarg_end[key] == 'false':
+ rlist.append('undo ' + dic_command[key])
+ elif kwarg_exist[key] == 'false' and kwarg_end[key] == 'true':
+ rlist.append(dic_command[key])
+ else:
+ rlist.append(dic_command[key] + ' ' + kwarg_end[key].lower())
+ return rlist
+
+
+class Lacp(object):
+ """
+ Manages Eth-Trunk interfaces LACP.
+ """
+
+ def __init__(self, argument_spec):
+ self.spec = argument_spec
+ self.module = None
+ self.init_module()
+
+ # module input info
+ self.trunk_id = self.module.params['trunk_id']
+ self.mode = self.module.params['mode']
+ self.param = dict()
+
+ self.state = self.module.params['state']
+
+ # state
+ self.changed = False
+ self.updates_cmd = list()
+ self.results = dict()
+ self.proposed = dict()
+ self.existing = dict()
+ self.end_state = dict()
+
+ def init_module(self):
+ """ init AnsibleModule """
+
+ self.module = AnsibleModule(
+ argument_spec=self.spec,
+ mutually_exclusive=[['trunk_id', 'global_priority']],
+ required_one_of=[['trunk_id', 'global_priority']],
+ supports_check_mode=True)
+
+ def check_params(self):
+ """check module params """
+ for key in self.module.params.keys():
+ if key in LACP.keys() and self.module.params[key] is not None:
+ self.param[key] = self.module.params[key]
+ if isinstance(self.module.params[key], bool):
+ self.param[key] = str(self.module.params[key]).lower()
+ msg = check_param(self.param)
+ if msg != 'ok':
+ self.module.fail_json(msg=msg)
+
+ def get_existing(self):
+ """get existing"""
+ xml_str = bulid_xml(self.param)
+ xml = get_nc_config(self.module, xml_str)
+ return xml_to_dict(xml)
+
+ def get_proposed(self):
+ """get proposed"""
+ proposed = dict(state=self.state)
+ proposed.update(self.param)
+ return proposed
+
+ def get_end_state(self):
+ """ get end_state"""
+ xml_str = bulid_xml(self.param)
+ xml = get_nc_config(self.module, xml_str)
+ return xml_to_dict(xml)
+
+ def work(self):
+ """worker"""
+
+ self.check_params()
+ existing = self.get_existing()
+ proposed = self.get_proposed()
+
+ # deal present or absent
+ if self.state == "present":
+ operation = 'merge'
+ else:
+ operation = 'delete'
+
+ xml_str = bulid_xml(self.param, operation=operation)
+ set_nc_config(self.module, xml_str)
+ end_state = self.get_end_state()
+
+ self.results['proposed'] = proposed
+ self.results['existing'] = existing
+ self.results['end_state'] = end_state
+ updates_cmd = compare_config(self.module, existing, end_state)
+ self.results['updates'] = updates_cmd
+ if updates_cmd:
+ self.results['changed'] = True
+ else:
+ self.results['changed'] = False
+
+ self.module.exit_json(**self.results)
+
+
+def main():
+
+ argument_spec = dict(
+ mode=dict(required=False,
+ choices=['Manual', 'Dynamic', 'Static'],
+ type='str'),
+ trunk_id=dict(required=False, type='int'),
+ preempt_enable=dict(required=False, type='bool'),
+ state_flapping=dict(required=False, type='bool'),
+ port_id_extension_enable=dict(required=False, type='bool'),
+ unexpected_mac_disable=dict(required=False, type='bool'),
+ system_id=dict(required=False, type='str'),
+ timeout_type=dict(required=False, type='str', choices=['Slow', 'Fast']),
+ fast_timeout=dict(required=False, type='int'),
+ mixed_rate_link_enable=dict(required=False, type='bool'),
+ preempt_delay=dict(required=False, type='int'),
+ collector_delay=dict(required=False, type='int'),
+ max_active_linknumber=dict(required=False, type='int'),
+ select=dict(required=False, type='str', choices=['Speed', 'Prority']),
+ priority=dict(required=False, type='int'),
+ global_priority=dict(required=False, type='int'),
+ state=dict(required=False, default='present',
+ choices=['present', 'absent'])
+ )
+
+ module = Lacp(argument_spec)
+ module.work()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/ce_lacp/defaults/main.yaml b/test/integration/targets/ce_lacp/defaults/main.yaml
new file mode 100644
index 00000000000..164afead284
--- /dev/null
+++ b/test/integration/targets/ce_lacp/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/test/integration/targets/ce_lacp/tasks/main.yaml b/test/integration/targets/ce_lacp/tasks/main.yaml
new file mode 100644
index 00000000000..cc27f174fd8
--- /dev/null
+++ b/test/integration/targets/ce_lacp/tasks/main.yaml
@@ -0,0 +1,2 @@
+---
+- { include: netconf.yaml, tags: ['netconf'] }
diff --git a/test/integration/targets/ce_lacp/tasks/netconf.yaml b/test/integration/targets/ce_lacp/tasks/netconf.yaml
new file mode 100644
index 00000000000..73b91adfaa2
--- /dev/null
+++ b/test/integration/targets/ce_lacp/tasks/netconf.yaml
@@ -0,0 +1,17 @@
+---
+- name: collect all netconf test cases
+ find:
+ paths: "{{ role_path }}/tests/netconf"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ connection: local
+ register: test_cases
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case (connection=netconf)
+ include: "{{ test_case_to_run }} ansible_connection=netconf"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/ce_lacp/tests/netconf/absent.yaml b/test/integration/targets/ce_lacp/tests/netconf/absent.yaml
new file mode 100644
index 00000000000..2c32e574d41
--- /dev/null
+++ b/test/integration/targets/ce_lacp/tests/netconf/absent.yaml
@@ -0,0 +1,95 @@
+---
+- debug:
+ msg: "START ce_lacp merged integration tests on connection={{ ansible_connection }}"
+# befor removing, it should be merged
+- include_tasks: merge.yaml
+
+- name: Merge the provided configuration with the exisiting running configuration
+ ce_lacp: &absent
+ mode: Dynamic
+ trunk_id: 10
+ preempt_enable: True
+ state_flapping: True
+ port_id_extension_enable: True
+ unexpected_mac_disable: True
+ system_id: 1111-2222-3333
+ timeout_type: Fast
+ fast_timeout: 12
+ mixed_rate_link_enable: True
+ preempt_delay: 12
+ collector_delay: 12
+ max_active_linknumber: 2
+ select: Prority
+ priority: 23
+ global_priority: 123
+ state: absent
+ register: result
+
+- name: Assert the configuration is reflected on host
+ assert:
+ that:
+ - "result['changed'] == true"
+
+- name: Get lacp config by ce_netconf.
+ ce_netconf:
+ rpc: get
+ cfg_xml: "
+
+
+
+ Eth-Trunk10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "
+ register: result_ifs_merged
+
+- name: Get lacp config by ce_netconf.
+ ce_netconf:
+ rpc: get
+ cfg_xml: "
+
+
+
+
+
+ "
+ register: result_global_merged
+
+
+- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
+ ce_lacp: *absent
+ register: result_re_merged
+
+- name: Assert that the previous task was idempotent, some become ot default values, others depend on devices.
+ assert:
+ that:
+ - "result_re_merged.changed == false"
+ - "'false' == result_ifs_merged.end_state.result"
+ - "'Slow' == result_ifs_merged.end_state.result"
+ - "'90' == result_ifs_merged.end_state.result"
+ - "'Prority' == result_ifs_merged.end_state.result"
+ - "'30' == result_ifs_merged.end_state.result"
+ - "'0' in result_ifs_merged.end_state.result"
+ - "'false' in result_ifs_merged.end_state.result"
+ - "'false' in result_ifs_merged.end_state.result"
+ - "'false' in result_ifs_merged.end_state.result"
+ - "'false' in result_ifs_merged.end_state.result"
+ - "'32768' in result_global_merged.end_state.result"
+
+- debug:
+ msg: "END ce_lacp merged integration tests on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/ce_lacp/tests/netconf/delete.yaml b/test/integration/targets/ce_lacp/tests/netconf/delete.yaml
new file mode 100644
index 00000000000..a3ce81723a9
--- /dev/null
+++ b/test/integration/targets/ce_lacp/tests/netconf/delete.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: "START ce_lacp deleted integration tests on connection={{ ansible_connection }}"
+
+- name: Merge the provided configuration with the exisiting running configuration
+ ce_lacp:
+ mode: Dynamic
+ trunk_id: 10
+ preempt_enable: True
+ state_flapping: True
+ port_id_extension_enable: True
+ unexpected_mac_disable: True
+ system_id: 1111-2222-3333
+ timeout_type: Fast
+ fast_timeout: 12
+ mixed_rate_link_enable: True
+ preempt_delay: 12
+ collector_delay: 12
+ max_active_linknumber: 2
+ select: Prority
+ priority: 23
+ global_priority: 123
+ state: absent
+ register: result
+
+- name: Assert the configuration is reflected on host
+ assert:
+ that:
+ - "result['changed'] == true"
+
+- debug:
+ msg: "END ce_lacp deleted integration tests on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/ce_lacp/tests/netconf/merge.yaml b/test/integration/targets/ce_lacp/tests/netconf/merge.yaml
new file mode 100644
index 00000000000..eef3956eeb1
--- /dev/null
+++ b/test/integration/targets/ce_lacp/tests/netconf/merge.yaml
@@ -0,0 +1,31 @@
+---
+- debug:
+ msg: "START ce_lacp merged integration tests on connection={{ ansible_connection }}"
+
+- name: Merge the provided configuration with the exisiting running configuration
+ ce_lacp:
+ mode: Dynamic
+ trunk_id: 10
+ preempt_enable: True
+ state_flapping: True
+ port_id_extension_enable: True
+ unexpected_mac_disable: True
+ system_id: 1111-2222-3333
+ timeout_type: Fast
+ fast_timeout: 12
+ mixed_rate_link_enable: True
+ preempt_delay: 12
+ collector_delay: 12
+ max_active_linknumber: 2
+ select: Prority
+ priority: 23
+ global_priority: 123
+ register: result
+
+- name: Assert the configuration is reflected on host
+ assert:
+ that:
+ - "result['changed'] == true"
+
+- debug:
+ msg: "END ce_lacp merged integration tests on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/ce_lacp/tests/netconf/present.yaml b/test/integration/targets/ce_lacp/tests/netconf/present.yaml
new file mode 100644
index 00000000000..ee696c9e1c0
--- /dev/null
+++ b/test/integration/targets/ce_lacp/tests/netconf/present.yaml
@@ -0,0 +1,103 @@
+---
+- debug:
+ msg: "START ce_lacp presented integration tests on connection={{ ansible_connection }}"
+
+- name: present the provided configuration with the exisiting running configuration
+ ce_lacp: &present
+ mode: Dynamic
+ trunk_id: 10
+ preempt_enable: True
+ state_flapping: True
+ port_id_extension_enable: True
+ unexpected_mac_disable: True
+ system_id: 1111-2222-3333
+ timeout_type: Fast
+ fast_timeout: 12
+ mixed_rate_link_enable: True
+ preempt_delay: 12
+ collector_delay: 12
+ max_active_linknumber: 2
+ select: Prority
+ priority: 23
+ global_priority: 123
+ register: result
+
+- name: Assert the configuration is reflected on host
+ assert:
+ that:
+ - "result['changed'] == true"
+
+- name: Get lacp config by ce_netconf.
+ ce_netconf:
+ rpc: get
+ cfg_xml: "
+
+
+
+ Eth-Trunk10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "
+ register: result_ifs_presentd
+
+- name: Get global lacp config by ce_netconf.
+ ce_netconf:
+ rpc: get
+ cfg_xml: "
+
+
+
+
+
+
+
+
+
+ "
+ register: result_global_presentd
+
+
+- name: present the provided configuration with the existing running configuration (IDEMPOTENT)
+ ce_lacp: *present
+ register: result_re_presentd
+
+- name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - "result_re_presentd.changed == false"
+ - "'Dynamic' == result_ifs_presentd.end_state.result"
+ - "'true' == result_ifs_presentd.end_state.result"
+ - "'Fast' == result_ifs_presentd.end_state.result"
+ - "'12' == result_ifs_presentd.end_state.result"
+ - "'Prority' == result_ifs_presentd.end_state.result"
+ - "'12' == result_ifs_presentd.end_state.result"
+ - "'2' == result_ifs_presentd.end_state.result"
+ - "'12' in result_ifs_presentd.end_state.result"
+ - "'true' in result_ifs_presentd.end_state.result"
+ - "'true' in result_ifs_presentd.end_state.result"
+ - "'true' in result_ifs_presentd.end_state.result"
+ - "'true' in result_ifs_presentd.end_state.result"
+ - "'true' in result_ifs_presentd.end_state.result"
+ - "'1111-2222-3333' in result_global_presentd.end_state.result"
+ - "'123' in result_global_presentd.end_state.result"
+
+# after present, it should be deleted
+- include_tasks: delete.yaml
+- debug:
+ msg: "END ce_lacp presentd integration tests on connection={{ ansible_connection }}"
diff --git a/test/units/modules/network/cloudengine/__init__.py b/test/units/modules/network/cloudengine/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/units/modules/network/cloudengine/ce_module.py b/test/units/modules/network/cloudengine/ce_module.py
new file mode 100644
index 00000000000..d7990d41384
--- /dev/null
+++ b/test/units/modules/network/cloudengine/ce_module.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2019 Red Hat
+#
+# This file is a 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 .
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import json
+from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase
+
+
+fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
+fixture_data = {}
+
+
+def load_fixture(module_name, name, device=''):
+ path = os.path.join(fixture_path, module_name, device, name)
+ if not os.path.exists(path):
+ path = os.path.join(fixture_path, module_name, name)
+
+ if path in fixture_data:
+ return fixture_data[path]
+
+ with open(path) as f:
+ data = f.read()
+
+ try:
+ data = json.loads(data)
+ except Exception:
+ pass
+
+ fixture_data[path] = data
+ return data
+
+
+class TestCloudEngineModule(ModuleTestCase):
+
+ def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False):
+
+ self.load_fixtures(commands)
+
+ if failed:
+ result = self.failed()
+ self.assertTrue(result['failed'], result)
+ else:
+ result = self.changed(changed)
+ self.assertEqual(result['changed'], changed, result)
+
+ if commands is not None:
+ if sort:
+ self.assertEqual(sorted(commands), sorted(result['commands']), result['commands'])
+ else:
+ self.assertEqual(commands, result['commands'], result['commands'])
+
+ return result
+
+ def failed(self):
+ with self.assertRaises(AnsibleFailJson) as exc:
+ self.module.main()
+
+ result = exc.exception.args[0]
+ self.assertTrue(result['failed'], result)
+ return result
+
+ def changed(self, changed=False):
+ with self.assertRaises(AnsibleExitJson) as exc:
+ self.module.main()
+
+ result = exc.exception.args[0]
+ self.assertEqual(result['changed'], changed, result)
+ return result
+
+ def load_fixtures(self, commands=None):
+ pass
diff --git a/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_00.txt b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_00.txt
new file mode 100644
index 00000000000..974c52c764a
--- /dev/null
+++ b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_00.txt
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Eth-Trunk10
+
+ false
+ Fast
+ 3
+ Speed
+ 30
+ 1
+ 0
+ false
+ false
+ false
+ 11-22-33
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_01.txt b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_01.txt
new file mode 100644
index 00000000000..03b3f31e347
--- /dev/null
+++ b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_01.txt
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Eth-Trunk10
+
+ true
+ Fast
+ 10
+ Speed
+ 130
+ 13
+ 12
+ true
+ true
+ true
+ 0000-1111-2222
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_10.txt b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_10.txt
new file mode 100644
index 00000000000..6abbbbfbe46
--- /dev/null
+++ b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_10.txt
@@ -0,0 +1,10 @@
+
+
+
+
+
+ 32768
+
+
+
+
\ No newline at end of file
diff --git a/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_11.txt b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_11.txt
new file mode 100644
index 00000000000..22260aa1e77
--- /dev/null
+++ b/test/units/modules/network/cloudengine/fixtures/ce_lacp/ce_lacp_11.txt
@@ -0,0 +1,10 @@
+
+
+
+
+
+ 32769
+
+
+
+
\ No newline at end of file
diff --git a/test/units/modules/network/cloudengine/test_ce_lacp.py b/test/units/modules/network/cloudengine/test_ce_lacp.py
new file mode 100644
index 00000000000..36ad6664bd4
--- /dev/null
+++ b/test/units/modules/network/cloudengine/test_ce_lacp.py
@@ -0,0 +1,134 @@
+# (c) 2019 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 .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+from units.compat.mock import patch
+from ansible.modules.network.cloudengine import ce_lacp
+from units.modules.utils import set_module_args
+from .ce_module import TestCloudEngineModule, load_fixture
+
+
+class TestCloudEngineLacpModule(TestCloudEngineModule):
+ module = ce_lacp
+
+ def setUp(self):
+ super(TestCloudEngineLacpModule, self).setUp()
+
+ self.mock_get_config = patch('ansible.modules.network.cloudengine.ce_lacp.get_nc_config')
+ self.get_nc_config = self.mock_get_config.start()
+
+ self.mock_set_config = patch('ansible.modules.network.cloudengine.ce_lacp.set_nc_config')
+ self.set_nc_config = self.mock_set_config.start()
+ self.set_nc_config.return_value = None
+
+ def tearDown(self):
+ super(TestCloudEngineLacpModule, self).tearDown()
+ self.mock_set_config.stop()
+ self.mock_get_config.stop()
+
+ def test_lacp_eturnk_present(self):
+ xml_existing = load_fixture('ce_lacp', 'ce_lacp_00.txt')
+ xml_end_state = load_fixture('ce_lacp', 'ce_lacp_01.txt')
+ update = ['lacp max active-linknumber 13',
+ 'lacp dampening state-flapping',
+ 'lacp port-id-extension enable',
+ 'lacp collector delay 12',
+ 'lacp preempt enable',
+ 'lacp system-id 0000-1111-2222',
+ 'lacp mixed-rate link enable',
+ 'lacp preempt delay 130',
+ 'lacp timeout user-defined 10',
+ 'lacp dampening unexpected-mac disable']
+ self.get_nc_config.side_effect = (xml_existing, xml_end_state)
+ set_module_args(dict(
+ mode='Dynamic',
+ trunk_id='10',
+ preempt_enable='true',
+ state_flapping='true',
+ port_id_extension_enable='true',
+ unexpected_mac_disable='true',
+ system_id='0000-1111-2222',
+ timeout_type='Fast',
+ fast_timeout='10',
+ mixed_rate_link_enable='true',
+ preempt_delay=11,
+ collector_delay=12,
+ max_active_linknumber=13,
+ select='Speed',
+ state='present'))
+ result = self.execute_module(changed=True)
+ self.assertEquals(sorted(result['updates']), sorted(update))
+
+ def test_lacp_eturnk_absent(self):
+ xml_existing = load_fixture('ce_lacp', 'ce_lacp_10.txt')
+ xml_end_state = load_fixture('ce_lacp', 'ce_lacp_00.txt')
+ default_values = ['undo lacp priority',
+ 'lacp timeout Fast',
+ 'lacp max active-linknumber 1',
+ 'lacp collector delay 0',
+ 'lacp preempt enable false',
+ 'lacp dampening state-flapping false',
+ 'lacp dampening unexpected-mac disable false',
+ 'lacp mixed-rate link enable false',
+ 'lacp port-id-extension enable false',
+ 'lacp preempt delay 30',
+ 'lacp select Speed',
+ 'lacp system-id 11-22-33',
+ 'lacp timeout user-defined 3']
+ self.get_nc_config.side_effect = (xml_existing, xml_end_state)
+ set_module_args(dict(
+ mode='Dynamic',
+ trunk_id='10',
+ preempt_enable='true',
+ state_flapping='true',
+ port_id_extension_enable='true',
+ unexpected_mac_disable='true',
+ system_id='0000-1111-2222',
+ timeout_type='Fast',
+ fast_timeout='10',
+ mixed_rate_link_enable='true',
+ preempt_delay=11,
+ collector_delay=12,
+ max_active_linknumber=13,
+ select='Speed',
+ state='absent'
+ ))
+ result = self.execute_module(changed=True)
+ self.assertEquals(sorted(result['updates']), sorted(default_values))
+
+ def test_lacp_global_present(self):
+ xml_existing = load_fixture('ce_lacp', 'ce_lacp_10.txt')
+ xml_end_state = load_fixture('ce_lacp', 'ce_lacp_11.txt')
+ self.get_nc_config.side_effect = (xml_existing, xml_end_state)
+ set_module_args(dict(global_priority=32769,
+ state='present'))
+ result = self.execute_module(changed=True)
+ self.assertEquals(result['updates'], ['lacp priority 32769'])
+
+ def test_lacp_global_absent(self):
+ xml_existing = load_fixture('ce_lacp', 'ce_lacp_11.txt')
+ xml_end_state = load_fixture('ce_lacp', 'ce_lacp_10.txt')
+ self.get_nc_config.side_effect = (xml_existing, xml_end_state)
+ set_module_args(dict(global_priority=32769,
+ state='absent'))
+ result = self.execute_module(changed=True)
+ # excpect: lacp priority is set to default value(32768)
+ self.assertEquals(result['updates'], ['lacp priority 32768'])