Contributing lib/ansible/modules/network/cloudengine/ce_file_copy.py module to manage HUAWEI data center CloudEngine (#22045)

* add ce_file_copy

add ce_file_copy

* fix review issue
pull/25047/head
QijunPan 7 years ago committed by John R Barker
parent 182d65d519
commit a137349522

@ -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 <http://www.gnu.org/licenses/>.
#
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 = """
<filter type="subtree">
<vfm xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
<dirs>
<dir>
<fileName>%s</fileName>
<dirName>%s</dirName>
<DirSize></DirSize>
</dir>
</dirs>
</vfm>
</filter>
"""
CE_NC_GET_SCP_ENABLE = """
<filter type="subtree">
<sshs xmlns="http://www.huawei.com/netconf/vrp" content-version="1.0" format-version="1.0">
<sshServer>
<scpEnable></scpEnable>
</sshServer>
</sshs>
</filter>
"""
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 "<data/>" 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 "<data/>" 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()
Loading…
Cancel
Save