From fd20eba6851d51b19177f467792f816488b9a99e Mon Sep 17 00:00:00 2001 From: Cristiano Urban Date: Mon, 23 Aug 2021 11:31:32 +0200 Subject: [PATCH] Added basic exception handling + improved logging. Signed-off-by: Cristiano Urban --- transfer_service/exceptions.py | 15 +- transfer_service/tape_client.py | 271 +++++++++++++++++++------------- 2 files changed, 177 insertions(+), 109 deletions(-) diff --git a/transfer_service/exceptions.py b/transfer_service/exceptions.py index cf52af2..9d05b19 100644 --- a/transfer_service/exceptions.py +++ b/transfer_service/exceptions.py @@ -10,7 +10,7 @@ class Error(Exception): class TarFileCreationException(Error): def __init__(self, folder): self.message = "Error: cannot create a .tar for " + folder - super(MultipleUsersException, self).__init__(self.message) + super(TarFileCreationException, self).__init__(self.message) # RapClient exceptions @@ -19,3 +19,16 @@ class MultipleUsersException(Error): def __init__(self): self.message = "Multiple users found with the same email address." super(MultipleUsersException, self).__init__(self.message) + + +# TapeClient exceptions + +class TapeClientException(Error): + def __init__(self, cmd, exitCode, errorMsg): + self.message = f"{cmd}\nexitCode: {exitCode}\nerrorMsg: {errorMsg}\n" + super(TapeClientException, self).__init__(self.message) + +class ScpInvalidFileException(Error): + def __init__(self): + self.message = "Error: invalid file or directory." + super(ScpInvalidFileException, self).__init__(self.message) diff --git a/transfer_service/tape_client.py b/transfer_service/tape_client.py index 6be19e0..6fedaa8 100644 --- a/transfer_service/tape_client.py +++ b/transfer_service/tape_client.py @@ -9,15 +9,17 @@ import sys import uuid from config import Config +from exceptions import ScpInvalidFileException +from exceptions import TapeClientException from redis_log_handler import RedisLogHandler from tape_pool import TapePool from tape_task import TapeTask class TapeClient(object): + # 'eeadm' command location on the tape library frontend EEADM = "/opt/ibm/ltfsee/bin/eeadm" - # Constructor def __init__(self, host, port, user, keyFile, logger): self.host = host self.port = port @@ -25,155 +27,208 @@ class TapeClient(object): self.logger = logger self.client = paramiko.SSHClient() self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - self.key = paramiko.RSAKey.from_private_key_file(keyFile) - self.client.load_system_host_keys() + self.keyFile = keyFile + #self.key = paramiko.RSAKey.from_private_key_file(keyFile) + #self.client.load_system_host_keys() self.scp = None self.taskList = [] self.poolList = [] def connect(self): """Connects to the tape library frontend.""" - self.client.connect(hostname = self.host, - port = self.port, - username = self.user, - pkey = self.key) + try: + self.key = paramiko.RSAKey.from_private_key_file(self.keyFile) + self.client.load_system_host_keys() + self.client.connect(hostname = self.host, + port = self.port, + username = self.user, + pkey = self.key) + except Exception: + self.logger.exception("Unable to establish SSH connection with tape library frontend.") + raise + finally: + self.client.disconnect() def getPoolList(self): """Returns a list of 'TapePool' objects.""" - stdin, stdout, stderr = self.client.exec_command(f"{self.EEADM} pool list --json") - exitCode = stdout.channel.recv_exit_status() - if not exitCode: - result = json.loads(stdout.readlines()[0].rstrip('\n')) - for el in result["payload"]: - pool = TapePool() - pool.id = el["id"] - pool.name = el["name"] - pool.mediaRestriction = el["media_restriction"] - pool.capacity = el["capacity"] - pool.usedSpace = el["used_space"] - pool.freeSpace = el["free_space"] - pool.reclaimableSpace = el["reclaimable_space"] - pool.activeSpace = el["active_space"] - pool.nonAppendableSpace = el["non_appendable_space"] - pool.numOfTapes = el["num_of_tapes"] - pool.formatClass = el["format_class"] - pool.libraryName = el["library_name"] - pool.libraryId = el["library_id"] - pool.nodeGroupName = el["nodegroup_name"] - pool.deviceType = el["device_type"] - pool.worm = el["worm"] - pool.fillPolicy = el["fill_policy"] - pool.owner = el["owner"] - pool.mountLimit = el["mount_limit"] - pool.lowSpaceWarningEnable = el["low_space_warning_enable"] - pool.lowSpaceWarningThreshold = el["low_space_warning_threshold"] - pool.noSpaceWarningEnable = el["no_space_warning_enable"] - pool.mode = el["mode"] - self.poolList.append(pool) - return self.poolList.copy() + cmd = f"{self.EEADM} pool list --json" + try: + stdin, stdout, stderr = self.client.exec_command(cmd) + except Exception: + self.logger.exception("Unable to execute command: '{cmd}'") + raise else: - sys.exit("cmd_exit_code = FAILURE") + exitCode = stdout.channel.recv_exit_status() + if not exitCode: + result = json.loads(stdout.readlines()[0].rstrip('\n')) + for el in result["payload"]: + pool = TapePool() + pool.id = el["id"] + pool.name = el["name"] + pool.mediaRestriction = el["media_restriction"] + pool.capacity = el["capacity"] + pool.usedSpace = el["used_space"] + pool.freeSpace = el["free_space"] + pool.reclaimableSpace = el["reclaimable_space"] + pool.activeSpace = el["active_space"] + pool.nonAppendableSpace = el["non_appendable_space"] + pool.numOfTapes = el["num_of_tapes"] + pool.formatClass = el["format_class"] + pool.libraryName = el["library_name"] + pool.libraryId = el["library_id"] + pool.nodeGroupName = el["nodegroup_name"] + pool.deviceType = el["device_type"] + pool.worm = el["worm"] + pool.fillPolicy = el["fill_policy"] + pool.owner = el["owner"] + pool.mountLimit = el["mount_limit"] + pool.lowSpaceWarningEnable = el["low_space_warning_enable"] + pool.lowSpaceWarningThreshold = el["low_space_warning_threshold"] + pool.noSpaceWarningEnable = el["no_space_warning_enable"] + pool.mode = el["mode"] + self.poolList.append(pool) + return self.poolList.copy() + else: + raise TapeClientException(cmd, exitCode, stderr) def getTaskList(self): """Returns the whole task list.""" - stdin, stdout, stderr = self.client.exec_command(f"{self.EEADM} task list --json") - exitCode = stdout.channel.recv_exit_status() - if not exitCode: - result = json.loads(stdout.readlines()[0].rstrip('\n')) - #print(result) - #print(len(result["payload"])) - #print(result["payload"][0]) - for el in result["payload"]: - task = TapeTask() - task.inUseTapes = el["inuse_tapes"] - task.inUsePools = el["inuse_pools"] - task.inUseNodeGroups = el["inuse_node_groups"] - task.inUseDrives = el["inuse_drives"] - task.cmdParam = el["cmd_param"] - task.result = el["result"] - task.status = el["status"] - task.completedTime = el["completed_time"] - task.startedTime = el["started_time"] - task.createdTime = el["created_time"] - task.setInUseLibs = el["inuse_libs"] - task.type = el["type"] - task.taskId = el["task_id"] - task.id = el["id"] - self.taskList.append(task) - return self.taskList.copy() + cmd = f"{self.EEADM} task list --json" + try: + stdin, stdout, stderr = self.client.exec_command(cmd) + except Exception: + self.logger.exception("Unable to execute command: '{cmd}'") + raise else: - sys.exit("cmd_exit_code = FAILURE") + exitCode = stdout.channel.recv_exit_status() + if not exitCode: + result = json.loads(stdout.readlines()[0].rstrip('\n')) + for el in result["payload"]: + task = TapeTask() + task.inUseTapes = el["inuse_tapes"] + task.inUsePools = el["inuse_pools"] + task.inUseNodeGroups = el["inuse_node_groups"] + task.inUseDrives = el["inuse_drives"] + task.cmdParam = el["cmd_param"] + task.result = el["result"] + task.status = el["status"] + task.completedTime = el["completed_time"] + task.startedTime = el["started_time"] + task.createdTime = el["created_time"] + task.setInUseLibs = el["inuse_libs"] + task.type = el["type"] + task.taskId = el["task_id"] + task.id = el["id"] + self.taskList.append(task) + return self.taskList.copy() + else: + raise TapeClientException(cmd, exitCode, stderr) def copy(self, srcPath, destPath): """Copies files/dirs recursively by passing their absolute paths.""" - self.scp = scp.SCPClient(self.client.get_transport()) - print(f"Copying {srcPath} in {destPath}") - if os.path.isdir(srcPath): - self.scp.put(srcPath, recursive = True, remote_path = destPath) - elif os.path.isfile(srcPath): - self.scp.put(srcPath, destPath) + try: + self.scp = scp.SCPClient(self.client.get_transport()) + except Exception: + self.logger.error("Unable to get transport from SSH client.") + raise else: - sys.exit("FATAL: invalid file/dir.") - self.scp.close() + self.logger.debug(f"Copying {srcPath} in {destPath}") + if os.path.isdir(srcPath): + self.scp.put(srcPath, recursive = True, remote_path = destPath) + elif os.path.isfile(srcPath): + self.scp.put(srcPath, destPath) + else: + self.logger.error("FATAL: invalid file/dir.") + raise ScpInvalidFileException + finally: + self.scp.close() def migrate(self, fileList, tapePool): """ Migrates to tape all files whose absolute path is contained in 'fileList'. """ - self.logger.debug("Starting MIGRATE operation...") + self.logger.info("Starting MIGRATE operation...") tmp = str(uuid.uuid1().hex) + "-vos_migrate.tmp" - fp = open(tmp, "a") - for f in fileList: - fp.write(f"{f}\n") - fp.close() - self.copy(f"./{tmp}", f"/tmp/{tmp}") - os.remove(f"./{tmp}") - cmd = f"{self.EEADM} migrate /tmp/{tmp} -p {tapePool}" - stdin, stdout, stderr = self.client.exec_command(cmd) - exitCode = stdout.channel.recv_exit_status() - if not exitCode: - self.logger.debug("MIGRATE operation COMPLETED.") + try: + fp = open(tmp, "a") + except IOError: + raise else: - self.logger.debug("MIGRATE operation FAILED.") - return exitCode + for f in fileList: + fp.write(f"{f}\n") + self.copy(f"./{tmp}", f"/tmp/{tmp}") + os.remove(f"./{tmp}") + cmd = f"{self.EEADM} migrate /tmp/{tmp} -p {tapePool}" + try: + stdin, stdout, stderr = self.client.exec_command(cmd) + except Exception: + self.logger.exception("Unable to execute command: '{cmd}'") + raise + else: + exitCode = stdout.channel.recv_exit_status() + if not exitCode: + self.logger.info("MIGRATE operation COMPLETED.") + else: + self.logger.error("MIGRATE operation FAILED.") + raise TapeClientException(cmd, exitCode, stderr) + return exitCode + finally: + fp.close() def recall(self, fileList): """ Recalls from tape all files whose absolute path is contained in 'fileList'. """ - self.logger.debug("Starting RECALL operation...") + self.logger.info("Starting RECALL operation...") tmp = str(uuid.uuid1().hex) + "-vos_recall.tmp" - fp = open(tmp, "a") - for f in fileList: - fp.write(f"{f}\n") - fp.close() - self.copy(f"./{tmp}", f"/tmp/{tmp}") - os.remove(f"./{tmp}") - cmd = f"{self.EEADM} recall /tmp/{tmp}" - stdin, stdout, stderr = self.client.exec_command(cmd) - exitCode = stdout.channel.recv_exit_status() - if not exitCode: - self.logger.debug("RECALL operation COMPLETED.") + try: + fp = open(tmp, "a") + except IOError: + raise else: - self.logger.debug("RECALL operation FAILED.") - return exitCode + for f in fileList: + fp.write(f"{f}\n") + self.copy(f"./{tmp}", f"/tmp/{tmp}") + os.remove(f"./{tmp}") + cmd = f"{self.EEADM} recall /tmp/{tmp}" + try: + stdin, stdout, stderr = self.client.exec_command(cmd) + except Exception: + self.logger.exception("Unable to execute command: '{cmd}'") + raise + else: + exitCode = stdout.channel.recv_exit_status() + if not exitCode: + self.logger.info("RECALL operation COMPLETED.") + else: + self.logger.error("RECALL operation FAILED.") + raise TapeClientException(cmd, exitCode, stderr) + return exitCode + finally: + fp.close() def recallChecksumFiles(self, dirName): """ Recursively recalls from tape all the checksum files related to the 'dirName' directory. """ - self.logger.debug("Starting RECALL_CHECKSUM operation...") + self.logger.info("Starting RECALL_CHECKSUM operation...") cmd = f"find $(dirname {dirName}) -type f \( -iname \"*-md5sum.txt\" \) | {self.EEADM} recall" - stdin, stdout, stderr = self.client.exec_command(cmd) - exitCode = stdout.channel.recv_exit_status() - if not exitCode: - self.logger.debug("RECALL_CHECKSUM operation COMPLETED.") + try: + stdin, stdout, stderr = self.client.exec_command(cmd) + except Exception: + self.logger.exception("Unable to execute command: '{cmd}'") + raise else: - self.logger.debug("RECALL_CHECKSUM operation FAILED.") - return exitCode + exitCode = stdout.channel.recv_exit_status() + if not exitCode: + self.logger.info("RECALL_CHECKSUM operation COMPLETED.") + else: + self.logger.error("RECALL_CHECKSUM operation FAILED.") + raise TapeClientException(cmd, exitCode, stderr) + return exitCode def disconnect(self): """Performs a cleanup and closes the connection.""" -- GitLab