diff --git a/lib/ansible/modules/network/cloudengine/ce_file_copy.py b/lib/ansible/modules/network/cloudengine/ce_file_copy.py
new file mode 100644
index 00000000000..17e3630ad12
--- /dev/null
+++ b/lib/ansible/modules/network/cloudengine/ce_file_copy.py
@@ -0,0 +1,395 @@
+#!/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 .
+#
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: ce_file_copy
+version_added: "2.4"
+short_description: Copy a file to a remote cloudengine device over SCP on HUAWEI CloudEngine switches.
+description:
+ - Copy a file to a remote cloudengine device over SCP on HUAWEI CloudEngine switches.
+author:
+ - Zhou Zhijin (@CloudEngine-Ansible)
+notes:
+ - The feature must be enabled with feature scp-server.
+ - If the file is already present, no transfer will take place.
+options:
+ local_file:
+ description:
+ - Path to local file. Local directory must exist.
+ The maximum length of local_file is 4096.
+ required: true
+ remote_file:
+ description:
+ - Remote file path of the copy. Remote directories must exist.
+ If omitted, the name of the local file will be used.
+ The maximum length of remote_file is 4096.
+ required: false
+ default: null
+ file_system:
+ description:
+ - The remote file system of the device. If omitted,
+ devices that support a file_system parameter will use
+ their default values.
+ File system indicates the storage medium and can be set to as follows,
+ 1) 'flash:' is root directory of the flash memory on the master MPU.
+ 2) 'slave#flash:' is root directory of the flash memory on the slave MPU.
+ If no slave MPU exists, this drive is unavailable.
+ 3) 'chassis ID/slot number#flash:' is root directory of the flash memory on
+ a device in a stack. For example, 1/5#flash indicates the flash memory
+ whose chassis ID is 1 and slot number is 5.
+ required: false
+ default: 'flash:'
+'''
+
+EXAMPLES = '''
+- name: File copy test
+ hosts: cloudengine
+ connection: local
+ gather_facts: no
+ vars:
+ cli:
+ host: "{{ inventory_hostname }}"
+ port: "{{ ansible_ssh_port }}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ transport: cli
+
+ tasks:
+
+ - name: "Copy a local file to remote device"
+ ce_file_copy:
+ local_file: /usr/vrpcfg.cfg
+ remote_file: /vrpcfg.cfg
+ file_system: 'flash:'
+ provider: "{{ cli }}"
+'''
+
+RETURN = '''
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: boolean
+ sample: true
+transfer_result:
+ description: information about transfer result.
+ returned: always
+ type: string
+ sample: 'The local file has been successfully transferred to the device.'
+local_file:
+ description: The path of the local file.
+ returned: always
+ type: string
+ sample: '/usr/work/vrpcfg.zip'
+remote_file:
+ description: The path of the remote file.
+ returned: always
+ type: string
+ sample: '/vrpcfg.zip'
+'''
+
+import re
+import os
+import time
+from xml.etree import ElementTree
+import paramiko
+from ansible.module_utils.shell import ShellError
+from ansible.module_utils.basic import get_exception, AnsibleModule
+from ansible.module_utils.ce import ce_argument_spec, run_commands, get_nc_config
+
+try:
+ from scp import SCPClient
+ HAS_SCP = True
+except ImportError:
+ HAS_SCP = False
+
+CE_NC_GET_FILE_INFO = """
+
+
+
+
+ %s
+ %s
+
+
+
+
+
+"""
+
+CE_NC_GET_SCP_ENABLE = """
+
+
+
+
+
+
+
+"""
+
+
+def get_cli_exception(exc=None):
+ """Get cli exception message"""
+
+ msg = list()
+ if not exc:
+ exc = get_exception()
+ if exc:
+ errs = str(exc).split("\r\n")
+ for err in errs:
+ if not err:
+ continue
+ if "matched error in response:" in err:
+ continue
+ if " at '^' position" in err:
+ err = err.replace(" at '^' position", "")
+ if err.replace(" ", "") == "^":
+ continue
+ if len(err) > 2 and err[0] in ["<", "["] and err[-1] in [">", "]"]:
+ continue
+ if err[-1] == ".":
+ err = err[:-1]
+ if err.replace(" ", "") == "":
+ continue
+ msg.append(err)
+ else:
+ msg = ["Error: Fail to get cli exception message."]
+
+ while msg[-1][-1] == ' ':
+ msg[-1] = msg[-1][:-1]
+
+ if msg[-1][-1] != ".":
+ msg[-1] += "."
+
+ return ", ".join(msg).capitalize()
+
+
+class FileCopy(object):
+ """File copy function class"""
+
+ def __init__(self, argument_spec):
+ self.spec = argument_spec
+ self.module = None
+ self.init_module()
+
+ # file copy parameters
+ self.local_file = self.module.params['local_file']
+ self.remote_file = self.module.params['remote_file']
+ self.file_system = self.module.params['file_system']
+
+ # state
+ self.transfer_result = None
+ self.changed = False
+
+ def init_module(self):
+ """Init module"""
+
+ self.module = AnsibleModule(
+ argument_spec=self.spec, supports_check_mode=True)
+
+ def remote_file_exists(self, dst, file_system='flash:'):
+ """Remote file whether exists"""
+
+ full_path = file_system + dst
+ file_name = os.path.basename(full_path)
+ file_path = os.path.dirname(full_path)
+ file_path = file_path + '/'
+ xml_str = CE_NC_GET_FILE_INFO % (file_name, file_path)
+ ret_xml = get_nc_config(self.module, xml_str)
+ if "" in ret_xml:
+ return False, 0
+
+ xml_str = ret_xml.replace('\r', '').replace('\n', '').\
+ replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\
+ replace('xmlns="http://www.huawei.com/netconf/vrp"', "")
+
+ # get file info
+ root = ElementTree.fromstring(xml_str)
+ topo = root.find("data/vfm/dirs/dir")
+ if topo is None:
+ return False, 0
+
+ for eles in topo:
+ if eles.tag in ["DirSize"]:
+ return True, int(eles.text.replace(',', ''))
+
+ return False, 0
+
+ def local_file_exists(self):
+ """Local file whether exists"""
+
+ return os.path.isfile(self.local_file)
+
+ def enough_space(self):
+ """Whether device has enough space"""
+
+ commands = list()
+ cmd = 'dir %s' % self.file_system
+ commands.append(cmd)
+ output = run_commands(self.module, commands)
+ if not output:
+ return True
+
+ match = re.search(r'\((.*) KB free\)', output[0])
+ kbytes_free = match.group(1)
+ kbytes_free = kbytes_free.replace(',', '')
+ file_size = os.path.getsize(self.local_file)
+ if int(kbytes_free) * 1024 > file_size:
+ return True
+
+ return False
+
+ def transfer_file(self, dest):
+ """Begin to transfer file by scp"""
+
+ if not self.local_file_exists():
+ self.module.fail_json(
+ msg='Could not transfer file. Local file doesn\'t exist.')
+
+ if not self.enough_space():
+ self.module.fail_json(
+ msg='Could not transfer file. Not enough space on device.')
+
+ hostname = self.module.params['provider']['host']
+ username = self.module.params['provider']['username']
+ password = self.module.params['provider']['password']
+ port = self.module.params['provider']['port']
+
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ ssh.connect(hostname=hostname, username=username, password=password, port=port)
+ full_remote_path = '{}{}'.format(self.file_system, dest)
+ scp = SCPClient(ssh.get_transport())
+ try:
+ scp.put(self.local_file, full_remote_path)
+ except:
+ time.sleep(10)
+ file_exists, temp_size = self.remote_file_exists(
+ dest, self.file_system)
+ file_size = os.path.getsize(self.local_file)
+ if file_exists and int(temp_size) == int(file_size):
+ pass
+ else:
+ scp.close()
+ self.module.fail_json(msg='Could not transfer file. There was an error '
+ 'during transfer. Please make sure the format of '
+ 'input parameters is right.')
+
+ scp.close()
+ return True
+
+ def get_scp_enable(self):
+ """Get scp enable state"""
+
+ xml_str = CE_NC_GET_SCP_ENABLE
+ ret_xml = get_nc_config(self.module, xml_str)
+ if "" in ret_xml:
+ return False
+
+ xml_str = ret_xml.replace('\r', '').replace('\n', '').\
+ replace('xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"', "").\
+ replace('xmlns="http://www.huawei.com/netconf/vrp"', "")
+
+ # get file info
+ root = ElementTree.fromstring(xml_str)
+ topo = root.find("data/sshs/sshServer")
+ if topo is None:
+ return False
+
+ for eles in topo:
+ if eles.tag in ["scpEnable"]:
+ return True, eles.text
+
+ return False
+
+ def work(self):
+ """Excute task """
+
+ if not HAS_SCP:
+ self.module.fail_json(
+ msg="'Error: No scp package, please install it.'")
+
+ if self.local_file and len(self.local_file) > 4096:
+ self.module.fail_json(
+ msg="'Error: The maximum length of local_file is 4096.'")
+
+ if self.remote_file and len(self.remote_file) > 4096:
+ self.module.fail_json(
+ msg="'Error: The maximum length of remote_file is 4096.'")
+
+ retcode, cur_state = self.get_scp_enable()
+ if retcode and cur_state == 'Disable':
+ self.module.fail_json(
+ msg="'Error: Please ensure SCP server is enabled.'")
+
+ if not os.path.isfile(self.local_file):
+ self.module.fail_json(
+ msg="Local file {} not found".format(self.local_file))
+
+ dest = self.remote_file or ('/' + os.path.basename(self.local_file))
+ remote_exists, file_size = self.remote_file_exists(
+ dest, file_system=self.file_system)
+ if remote_exists and (os.path.getsize(self.local_file) != file_size):
+ remote_exists = False
+
+ if not remote_exists:
+ self.changed = True
+ file_exists = False
+ else:
+ file_exists = True
+ self.transfer_result = 'The local file already exists on the device.'
+
+ if not file_exists:
+ try:
+ self.transfer_file(dest)
+ self.transfer_result = 'The local file has been successfully ' \
+ 'transferred to the device.'
+ except ShellError:
+ clie = get_exception()
+ self.module.fail_json(msg=get_cli_exception(clie))
+
+ if self.remote_file is None:
+ self.remote_file = '/' + os.path.basename(self.local_file)
+
+ self.module.exit_json(
+ changed=self.changed,
+ transfer_result=self.transfer_result,
+ local_file=self.local_file,
+ remote_file=self.remote_file,
+ file_system=self.file_system)
+
+
+def main():
+ """Main function entry"""
+
+ argument_spec = dict(
+ local_file=dict(required=True),
+ remote_file=dict(required=False),
+ file_system=dict(required=False, default='flash:')
+ )
+ argument_spec.update(ce_argument_spec)
+ filecopy_obj = FileCopy(argument_spec)
+ filecopy_obj.work()
+
+
+if __name__ == '__main__':
+ main()