Source code for pyhpecw7.features.file_copy

"""Manage file transfer to HPCOM7 devices.
"""
from scp import SCPClient
from lxml import etree
from pyhpecw7.utils.xml.lib import *
from pyhpecw7.features.errors import FileNotEnoughSpaceError,\
    FileNotReadableError, FileRemoteDirDoesNotExist, FileTransferError, FileHashMismatchError
from pyhpecw7.errors import NCError

import paramiko
import hashlib
import re
import os


[docs]class FileCopy(object): """This class is used to copy local files to a ``HPCOM7`` device. Note: SCP should first be enabled on the device. Note: When using this class, the passed in ``HPCOM7`` object should be constructed with the ``timeout`` equal to at least 60 seconds. Remote MD5 sum calculations can take some time. Note: If the remote directory doesn't exist (check ``remote_dir_exists``), it's the responsibility of the user to call ``create_remote_dir()`` before calling ``transfer_file()``. Otherwise, a ``FileRemoteDirDoesNotExist`` exception will be raised. Args: device (HPCOM7): connected instance of a ``pyhpecw7.comware.HPCOM7`` object. src (str): Full path to local file to be copied. dst (str): OPTIONAL - Full path or filename of remote file. If just a filename is supplied, 'flash:/' will be prepended. If nothing is supplied, the source filename will be used, and 'flash:/' will be prepended. port (int): OPTIONAL - The SSH port over which the SCP connection is made. Defaults to 22. Attributes: device (HPCOM7): connected instance of a ``pyhpecw7.comware.HPCOM7`` object. src (str): Full path to local file to be copied. dst (str): Full path of remote file. port (int): The SSH port over which the SCP connection is made. remote_dir_exists (bool): Whether there remote directory exists. """ def __init__(self, device, src, dst=None, port=22): self.device = device self.src = src self.dst = dst or os.path.basename(src) if self.dst.find(':/') < 0: self._remote_dir = 'flash:/' self.dst = self._remote_dir + self.dst else: self._remote_dir = '/'.join( self.dst.split('/')[:-1]) + '/' if self._remote_dir[-2:] == ':/': self.remote_dir_exists = True else: self.remote_dir_exists = self._remote_dir_exists() self.port = port def _get_flash_size(self): """Return the available space in the remote directory. """ rsp = self.device.cli_display('dir ' + self._remote_dir) match = re.search(r'\((\d+)(\s+KB\s+free\))', rsp) try: return int(match.group(1)) * 1000 except: return 0 def _enough_space(self): """Check for enough space on the remote device. Raises: FileNotEnoughSpaceError: if there isn't enough space on the remote device. """ flash_size = self._get_flash_size() file_size = os.path.getsize(self.src) if file_size > flash_size: raise FileNotEnoughSpaceError(self.src, file_size, flash_size)
[docs] def file_already_exists(self): """Check to see if there is a remote file with the same name and md5 sum. Returns: ``True`` if exists, ``False`` otherwise. """ if not self.remote_dir_exists: return False try: dst_hash = self._get_remote_md5() except NCError: return False src_hash = self._get_local_md5() if src_hash == dst_hash: return True return False
def _safety_checks(self): """Check to make sure the source file exists, and that there's enough space on the device. Throws: FileNotReadableError: if the local file doesn't exist or isn't readable. """ try: f = open(self.src, "rb") except IOError: raise FileNotReadableError(self.src) finally: try: f.close() except NameError: pass if not self.remote_dir_exists: raise FileRemoteDirDoesNotExist(self._remote_dir) if self.remote_dir_exists: if not self.file_already_exists(): self._enough_space() def _get_remote_md5(self): """Return the md5 sum of the remote file, if it exists. """ E = action_element_maker() top = E.top( E.FileSystem( E.Files( E.File( E.SrcName(self.dst), E.Operations( E.md5sum() ) ) ) ) ) nc_get_reply = self.device.action(top) reply_ele = etree.fromstring(nc_get_reply.xml.encode('ascii')) md5sum = find_in_action('md5sum', reply_ele) if md5sum is not None: return md5sum.text.strip() def _get_local_md5(self, blocksize=2**20): """Get the md5 sum of the local file, if it exists. """ m = hashlib.md5() with open(self.src, "rb") as f: buf = f.read(blocksize) while buf: m.update(buf) buf = f.read(blocksize) return m.hexdigest() def _remote_dir_exists(self): """Check to see if the remote directory exists. """ E = data_element_maker() top = E.top( E.FileSystem( E.Files( E.File( E.Name(self._remote_dir.rstrip('/')), E.IsDirectory() ) ) ) ) nc_get_reply = self.device.get(('subtree', top)) reply_ele = etree.fromstring(nc_get_reply.xml.encode('ascii')) is_dir = find_in_data('IsDirectory', reply_ele) if is_dir is not None\ and is_dir.text == 'true': return True return False
[docs] def create_remote_dir(self): """Create the remote directory. Raises: FileCreateDirectoryError: if the directory could not be created. """ E = action_element_maker() top = E.top( E.FileSystem( E.Files( E.File( E.SrcName(self._remote_dir.strip('/')), E.Operations( E.MkDir() ) ) ) ) ) nc_get_reply = self.device.action(top) reply_ele = etree.fromstring(nc_get_reply.xml.encode('ascii')) self.remote_dir_exists = True
[docs] def transfer_file(self, hostname=None, username=None, password=None): """Transfer the file to the remote device over SCP. Note: If any arguments are omitted, the corresponding attributes of the ``self.device`` will be used. Args: hostname (str): OPTIONAL - The name or IP address of the remote device. username (str): OPTIONAL - The SSH username for the remote device. password (str): OPTIONAL - The SSH password for the remote device. Raises: FileTransferError: if an error occurs during the file transfer. FileHashMismatchError: if the source and destination hashes don't match. FileNotReadableError: if the local file doesn't exist or isn't readable. FileNotEnoughSpaceError: if there isn't enough space on the device. FileRemoteDirDoesNotExist: if the remote directory doesn't exist. """ self._safety_checks() hostname = hostname or self.device.host username = username or self.device.username password = password or self.device.password ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect( hostname=hostname, username=username, password=password, port=self.port, allow_agent=False, look_for_keys=False) scp = SCPClient(ssh.get_transport()) try: scp.put(self.src, self.dst) except: raise FileTransferError scp.close() src_hash = self._get_local_md5() dst_hash = self._get_remote_md5() if src_hash != dst_hash: raise FileHashMismatchError(self.src, self.dst, src_hash, dst_hash)