diff --git a/lib/ansible/modules/network/cloudengine/ce_multicast_global.py b/lib/ansible/modules/network/cloudengine/ce_multicast_global.py
new file mode 100644
index 00000000000..b6b1af26a46
--- /dev/null
+++ b/lib/ansible/modules/network/cloudengine/ce_multicast_global.py
@@ -0,0 +1,290 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ce_multicast_global
+version_added: "2.10"
+author: xuxiaowei0512 (@xuxiaowei0512)
+short_description: Manages multicast global configuration on HUAWEI CloudEngine switches.
+description:
+ - Manages multicast global on HUAWEI CloudEngine switches.
+notes:
+ - If no vrf is supplied, vrf is set to default.
+ - If I(state=absent), the route will be removed, regardless of the non-required parameters.
+ - This module requires the netconf system service be enabled on the remote device being managed.
+ - This module works with connection C(netconf).
+options:
+ aftype:
+ description:
+ - Destination ip address family type of static route.
+ required: true
+ type: str
+ choices: ['v4','v6']
+ vrf:
+ description:
+ - VPN instance of destination ip address.
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ type: str
+ default: present
+ choices: ['present','absent']
+'''
+
+EXAMPLES = '''
+---
+ - name: multicast routing-enable
+ ce_multicast_global:
+ aftype: v4
+ state: absent
+ provider: "{{ cli }}"
+ - name: multicast routing-enable
+ ce_multicast_global:
+ aftype: v4
+ state: present
+ provider: "{{ cli }}"
+ - name: multicast routing-enable
+ ce_multicast_global:
+ aftype: v4
+ vrf: vrf1
+ provider: "{{ cli }}"
+
+'''
+RETURN = '''
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"addressFamily": "ipv4unicast", "state": "present", "vrfName": "_public_"}
+existing:
+ description: k/v pairs of existing switchport
+ returned: always
+ type: dict
+ sample: {}
+end_state:
+ description: k/v pairs of switchport after module execution
+ returned: always
+ type: dict
+ sample: {"addressFamily": "ipv4unicast", "state": "present", "vrfName": "_public_"}
+updates:
+ description: command list sent to the device
+ returned: always
+ type: list
+ sample: ["multicast routing-enable"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+'''
+
+from xml.etree import ElementTree
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network.cloudengine.ce import get_nc_config, set_nc_config
+
+CE_NC_GET_MULTICAST_GLOBAL = """
+
+
+
+
+ %s
+ %s
+
+
+
+
+"""
+CE_NC_MERGE_MULTICAST_GLOBAL = """
+
+
+
+ %s
+ %s
+
+
+
+"""
+CE_NC_DELETE_MULTICAST_GLOBAL = """
+
+
+
+ %s
+ %s
+
+
+
+"""
+
+
+def build_config_xml(xmlstr):
+ """build config xml"""
+
+ return ' ' + xmlstr + ' '
+
+
+class MulticastGlobal(object):
+ """multicast global module"""
+
+ def __init__(self, argument_spec):
+ """multicast global info"""
+ self.spec = argument_spec
+ self.module = None
+ self._initmodule_()
+
+ self.aftype = self.module.params['aftype']
+ self.state = self.module.params['state']
+ if self.aftype == "v4":
+ self.version = "ipv4unicast"
+ else:
+ self.version = "ipv6unicast"
+ # vpn instance info
+ self.vrf = self.module.params['vrf']
+ if self.vrf is None:
+ self.vrf = "_public_"
+ # state
+ self.changed = False
+ self.updates_cmd = list()
+ self.results = dict()
+ self.proposed = dict()
+ self.existing = dict()
+ self.end_state = dict()
+
+ self.multicast_global_info = dict()
+
+ def _initmodule_(self):
+ """init module"""
+ self.module = AnsibleModule(
+ argument_spec=self.spec, supports_check_mode=False)
+
+ def _checkresponse_(self, xml_str, xml_name):
+ """check if response message is already succeed."""
+
+ if "" not in xml_str:
+ self.module.fail_json(msg='Error: %s failed.' % xml_name)
+
+ def set_change_state(self):
+ """set change state"""
+ state = self.state
+ change = False
+ self.get_multicast_global()
+ # new or edit
+ if state == 'present':
+ if not self.multicast_global_info.get('multicast_global'):
+ # i.e. self.multicast_global_info['multicast_global'] has not value
+ change = True
+ else:
+ # delete
+ if self.multicast_global_info.get('multicast_global'):
+ # i.e. self.multicast_global_info['multicast_global'] has value
+ change = True
+ self.changed = change
+
+ def get_multicast_global(self):
+ """get one data"""
+ self.multicast_global_info["multicast_global"] = list()
+ getxmlstr = CE_NC_GET_MULTICAST_GLOBAL % (
+ self.version, self.vrf)
+ xml_str = get_nc_config(self.module, getxmlstr)
+ if 'data/' in xml_str:
+ return
+ xml_str = xml_str.replace('\r', '').replace('\n', ''). \
+ replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', ""). \
+ replace('xmlns="http://www.huawei.com/netconf/vrp"', "")
+ root = ElementTree.fromstring(xml_str)
+ mcast_enable = root.findall(
+ "mcastbase/mcastAfsEnables/mcastAfsEnable")
+ if mcast_enable:
+ # i.e. mcast_enable = [{vrfName:11,addressFamily:'xx'},{vrfName:22,addressFamily:'xx'}...]
+ for mcast_enable_key in mcast_enable:
+ # i.e. mcast_enable_key = {vrfName:11,addressFamily:'xx'}
+ mcast_info = dict()
+ for ele in mcast_enable_key:
+ if ele.tag in ["vrfName", "addressFamily"]:
+ mcast_info[ele.tag] = ele.text
+ self.multicast_global_info['multicast_global'].append(mcast_info)
+
+ def get_existing(self):
+ """get existing information"""
+ self.set_change_state()
+ self.existing["multicast_global"] = self.multicast_global_info["multicast_global"]
+
+ def get_proposed(self):
+ """get proposed information"""
+ self.proposed['addressFamily'] = self.version
+ self.proposed['state'] = self.state
+ self.proposed['vrfName'] = self.vrf
+
+ def set_multicast_global(self):
+ """set multicast global"""
+ if not self.changed:
+ return
+ version = self.version
+ state = self.state
+ if state == "present":
+ configxmlstr = CE_NC_MERGE_MULTICAST_GLOBAL % (self.vrf, version)
+ else:
+ configxmlstr = CE_NC_DELETE_MULTICAST_GLOBAL % (self.vrf, version)
+
+ conf_str = build_config_xml(configxmlstr)
+ recv_xml = set_nc_config(self.module, conf_str)
+ self._checkresponse_(recv_xml, "SET_MULTICAST_GLOBAL")
+
+ def set_update_cmd(self):
+ """set update command"""
+ if not self.changed:
+ return
+ if self.state == "present":
+ self.updates_cmd.append('multicast routing-enable')
+ else:
+ self.updates_cmd.append('undo multicast routing-enable')
+
+ def get_end_state(self):
+ """get end state information"""
+ self.get_multicast_global()
+ self.end_state["multicast_global"] = self.multicast_global_info["multicast_global"]
+
+ def work(self):
+ """worker"""
+ self.get_existing()
+ self.get_proposed()
+ self.set_multicast_global()
+ self.set_update_cmd()
+ self.get_end_state()
+ self.results['changed'] = self.changed
+ self.results['existing'] = self.existing
+ self.results['proposed'] = self.proposed
+ self.results['end_state'] = self.end_state
+ if self.changed:
+ self.results['updates'] = self.updates_cmd
+ else:
+ self.results['updates'] = list()
+ self.module.exit_json(**self.results)
+
+
+def main():
+ """main"""
+
+ argument_spec = dict(
+ aftype=dict(choices=['v4', 'v6'], required=True),
+ vrf=dict(required=False, type='str'),
+ state=dict(choices=['absent', 'present'], default='present', required=False),
+ )
+ interface = MulticastGlobal(argument_spec)
+ interface.work()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/ce_multicast_global/defaults/main.yaml b/test/integration/targets/ce_multicast_global/defaults/main.yaml
new file mode 100644
index 00000000000..164afead284
--- /dev/null
+++ b/test/integration/targets/ce_multicast_global/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/test/integration/targets/ce_multicast_global/tasks/main.yaml b/test/integration/targets/ce_multicast_global/tasks/main.yaml
new file mode 100644
index 00000000000..cc27f174fd8
--- /dev/null
+++ b/test/integration/targets/ce_multicast_global/tasks/main.yaml
@@ -0,0 +1,2 @@
+---
+- { include: netconf.yaml, tags: ['netconf'] }
diff --git a/test/integration/targets/ce_multicast_global/tasks/netconf.yaml b/test/integration/targets/ce_multicast_global/tasks/netconf.yaml
new file mode 100644
index 00000000000..73b91adfaa2
--- /dev/null
+++ b/test/integration/targets/ce_multicast_global/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_multicast_global/tests/netconf/test_ce_multicast_global.yaml b/test/integration/targets/ce_multicast_global/tests/netconf/test_ce_multicast_global.yaml
new file mode 100644
index 00000000000..69b1f5f26bc
--- /dev/null
+++ b/test/integration/targets/ce_multicast_global/tests/netconf/test_ce_multicast_global.yaml
@@ -0,0 +1,73 @@
+---
+- debug:
+ msg: "START ce_multicast_global presented integration tests on connection={{ ansible_connection }}"
+
+- name: present the provided configuration with the exisiting running configuration
+ ce_multicast_global: &present
+ aftype: v4
+ vrf: vpna
+ weight: 100
+ register: result
+
+- name: Assert the configuration is reflected on host
+ assert:
+ that:
+ - "result['changed'] == true"
+
+- name: Get basic config by ce_netconf.
+ ce_netconf: &get_config
+ rpc: get
+ cfg_xml: "
+
+
+
+
+
+
+
+
+ "
+ register: result_xml
+
+
+- name: present the provided configuration with the existing running configuration (IDEMPOTENT)
+ ce_multicast_global: *present
+ register: repeat
+
+- name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - "repeat.changed == false"
+ - "'vpna' in result_xml.end_state.result"
+ - "'vpna' in result_xml.end_state.result"
+
+- name: present the provided configuration with the exisiting running configuration
+ ce_multicast_global: &absent
+ aftype: v4
+ vrf: vpna
+ register: result
+
+
+- name: Assert the configuration is reflected on host
+ assert:
+ that:
+ - "result['changed'] == true"
+
+- name: present the provided configuration with the existing running configuration (IDEMPOTENT)
+ ce_multicast_global: *absent
+ register: repeat
+
+- name: Get basic config by ce_netconf.
+ ce_netconf: *get_config
+ register: result_xml
+
+- name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - "repeat.changed == false"
+ - "'vpna' not in result_xml.end_state.result"
+ - "'vpna' not in result_xml.end_state.result"
+# after present, isis 100 should be deleted
+
+- debug:
+ msg: "END ce_multicast_global resentd integration tests on connection={{ ansible_connection }}"
diff --git a/test/units/modules/network/cloudengine/fixtures/ce_multicast_global/after.txt b/test/units/modules/network/cloudengine/fixtures/ce_multicast_global/after.txt
new file mode 100644
index 00000000000..b196031e7b1
--- /dev/null
+++ b/test/units/modules/network/cloudengine/fixtures/ce_multicast_global/after.txt
@@ -0,0 +1,10 @@
+
+
+
+
+ vpna
+ ipv4unicast
+
+
+
+
diff --git a/test/units/modules/network/cloudengine/fixtures/ce_multicast_global/before.txt b/test/units/modules/network/cloudengine/fixtures/ce_multicast_global/before.txt
new file mode 100644
index 00000000000..fe6c8395715
--- /dev/null
+++ b/test/units/modules/network/cloudengine/fixtures/ce_multicast_global/before.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/units/modules/network/cloudengine/test_ce_multicast_global.py b/test/units/modules/network/cloudengine/test_ce_multicast_global.py
new file mode 100644
index 00000000000..5cc9f5ead3d
--- /dev/null
+++ b/test/units/modules/network/cloudengine/test_ce_multicast_global.py
@@ -0,0 +1,69 @@
+# (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_multicast_global
+from units.modules.network.cloudengine.ce_module import TestCloudEngineModule, load_fixture
+from units.modules.utils import set_module_args
+
+
+class TestCloudEngineLacpModule(TestCloudEngineModule):
+ module = ce_multicast_global
+
+ def setUp(self):
+ super(TestCloudEngineLacpModule, self).setUp()
+
+ self.mock_get_config = patch('ansible.modules.network.cloudengine.ce_multicast_global.get_nc_config')
+ self.get_nc_config = self.mock_get_config.start()
+
+ self.mock_set_config = patch('ansible.modules.network.cloudengine.ce_multicast_global.set_nc_config')
+ self.set_nc_config = self.mock_set_config.start()
+ self.set_nc_config.return_value = ""
+ self.before = load_fixture('ce_multicast_global', 'before.txt')
+ self.after = load_fixture('ce_multicast_global', 'after.txt')
+
+ def tearDown(self):
+ super(TestCloudEngineLacpModule, self).tearDown()
+ self.mock_set_config.stop()
+ self.mock_get_config.stop()
+
+ def test_multicast_enable(self):
+ update = ['multicast routing-enable']
+ self.get_nc_config.side_effect = (self.before, self.after)
+ set_module_args(dict(
+ aftype='v4',
+ vrf='vpna',
+ state='present')
+ )
+ result = self.execute_module(changed=True)
+ self.assertEquals(sorted(result['updates']), sorted(update))
+
+ def test_multicast_undo_enable(self):
+ update = ['undo multicast routing-enable']
+ self.get_nc_config.side_effect = (self.after, self.before)
+ set_module_args(dict(
+ aftype='v4',
+ vrf='vpna',
+ state='absent')
+ )
+ result = self.execute_module(changed=True)
+ self.assertEquals(sorted(result['updates']), sorted(update))