diff --git a/software/software/db/__init__.py b/software/software/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/software/software/db/api.py b/software/software/db/api.py new file mode 100644 index 00000000..668cce43 --- /dev/null +++ b/software/software/db/api.py @@ -0,0 +1,54 @@ +from software.software_entities import DeployHandler +from software.software_entities import DeployHostHandler + +def get_instance(): + """Return a Software API instance.""" + return SoftwareAPI() + +class SoftwareAPI: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(SoftwareAPI, cls).__new__(cls) + return cls._instance + + def __init__(self): + self.deploy_handler = DeployHandler() + self.deploy_host_handler = DeployHostHandler() + + def create_deploy(self, from_release, to_release, reboot_required): + self.deploy_handler.create(from_release, to_release, reboot_required) + + def get_deploy(self, from_release, to_release): + return self.deploy_handler.query(from_release, to_release) + + def get_deploy_all(self): + return self.deploy_handler.query_all() + + def update_deploy(self, from_release, to_release, reboot_required, state): + self.deploy_handler.update(from_release, to_release, reboot_required, state) + + def delete_deploy(self, from_release, to_release): + self.deploy_handler.delete(from_release, to_release) + + def create_deploy_host(self, hostname, software_release, target_release): + self.deploy_host_handler.create(hostname, software_release, target_release) + + def get_deploy_host_by_hostname(self, hostname): + return self.deploy_host_handler.query_by_hostname(hostname) + + def get_deploy_host(self): + return self.deploy_host_handler.query_all() + + def update_deploy_host(self, hostname, software_release, target_release, state): + self.deploy_host_handler.update(hostname, software_release, target_release, state) + + def delete_deploy_host_by_hostname(self, hostname): + self.deploy_host_handler.delete_by_hostname(hostname) + + def delete_deploy_host(self, hostname, software_release, target_release): + self.deploy_host_handler.delete(hostname, software_release, target_release) + + def delete_deploy_host_all(self): + self.deploy_host_handler.delete_all() diff --git a/software/software/exceptions.py b/software/software/exceptions.py index 5dee1006..4669137a 100644 --- a/software/software/exceptions.py +++ b/software/software/exceptions.py @@ -95,3 +95,15 @@ class SysinvClientNotInitialized(SoftwareError): class StateValidationFailure(SoftwareError): """State Validation Failure""" pass + +class DeployDoNotExist(SoftwareError): + """Deploy Do Not Exist""" + pass + +class DeployAlreadyExist(SoftwareError): + """Deploy Already Exist""" + pass + +class ReleaseVersionDoNotExist(SoftwareError): + """Release Version Do Not Exist""" + pass diff --git a/software/software/software_entities.py b/software/software/software_entities.py index ba5d8e02..1dd24c0d 100644 --- a/software/software/software_entities.py +++ b/software/software/software_entities.py @@ -10,7 +10,9 @@ from abc import ABC, abstractmethod from enum import Enum from typing import List -from software.utils import check_instances, check_state +from software import constants +from software.exceptions import DeployDoNotExist, DeployAlreadyExist +from software.utils import check_instances, check_state, save_to_json_file, get_software_filesystem_data LOG = logging.getLogger('main_logger') @@ -130,17 +132,22 @@ class Deploy(ABC): 'upgrade-hosts') @abstractmethod - def create(self, from_release: str, to_release: str, state: str): + def create(self, from_release: str, to_release: str, reboot_required:bool, state: str): """ Create a new deployment entry. :param from_release: The source release version. :param to_release: The target release version. + :param reboot_required: If is required to do host reboot. :param state: The state of the deployment. """ - check_instances([from_release, to_release, state], str) - check_state(state, self.states) + instances = [from_release, to_release] + if state: + check_state(state, self.states) + instances.append(state) + check_instances([reboot_required], bool) + check_instances(instances, str) pass @abstractmethod @@ -156,16 +163,18 @@ class Deploy(ABC): pass @abstractmethod - def update(self, from_release: str, to_release: str, state: str): + def update(self, from_release: str, to_release: str, reboot_required:bool, state: str): """ Update a deployment entry. :param from_release: The source release version. :param to_release: The target release version. + :param reboot_required: If is required to do host reboot. :param state: The state of the deployment. """ check_instances([from_release, to_release, state], str) + check_instances([reboot_required], bool) check_state(state, self.states) pass @@ -187,51 +196,225 @@ class DeployHosts(ABC): self.states = Enum('States', 'completed failed pending ready') @abstractmethod - def create(self, host_name: str, software_release: str, target_release: str, state: str): + def create(self, hostname: str, software_release: str, target_release: str, state: str): """ Create a new deploy-host entry - :param host_name: The name of the host. + :param hostname: The name of the host. :param software_release: The software release version. :param target_release: The target release version. :param state: The state of the deploy-host entry. """ - check_instances([host_name, software_release, target_release, state], str) - check_state(state, self.states) + instances = [hostname, software_release, target_release] + if state: + check_state(state, self.states) + instances.append(state) + check_instances(instances, str) pass @abstractmethod - def query(self, host_name: str): + def query_by_hostname(self, hostname: str): """ Get deploy-host entries for a given host. - :param host_name: The name of the host. + :param hostname: The name of the host. """ - check_instances([host_name], str) + check_instances([hostname], str) pass @abstractmethod - def update(self, host_name: str, software_release: str, target_release: str, state: str): + def query(self, hostname: str, software_release: str, target_release: str): """ - update a deploy-host entry + Get deploy-host entries for a given host. - :param host_name: The name of the host. + :param hostname: The name of the host. + :param software_release: The software release version. + :param target_release: The target release version. + + """ + check_instances([hostname, software_release, target_release], str) + pass + + @abstractmethod + def update(self, hostname: str, software_release: str, target_release: str, state: str): + """ + Update a deploy-host entry + + :param hostname: The name of the host. :param software_release: The software release version. :param target_release: The target release version. :param state: The state of the deploy-host entry. """ - check_instances([host_name, software_release, target_release, state], str) + check_instances([hostname, software_release, target_release, state], str) check_state(state, self.states) pass @abstractmethod - def delete(self, host_name: str): + def delete_by_hostname(self, hostname: str): """ Delete deploy-host entries for a given host. - :param host_name: The name of the host. + :param hostname: The name of the host. """ - check_instances([host_name], str) + check_instances([hostname], str) pass + + @abstractmethod + def delete(self, hostname, software_release, target_release): + """ + Delete deploy-host entries for a given host. + + :param hostname: The name of the host. + :param software_release: The software release version. + :param target_release: The target release version. + """ + check_instances([hostname, software_release, target_release], str) + pass + +class DeployHandler(Deploy): + def __init__(self): + super().__init__() + self.data = get_software_filesystem_data() + + def create(self, from_release, to_release, reboot_required, state=None): + super().create(from_release, to_release, reboot_required, state) + deploy = self.query(from_release, to_release) + if deploy: + raise DeployAlreadyExist("Error to create. Deploy already exist.") + new_deploy = { + "from_release": from_release, + "to_release": to_release, + "reboot_required": reboot_required, + "state": state + } + deploy_data = self.data.get("deploy", []) + if not deploy_data: + deploy_data = { + "deploy": [] + } + deploy_data["deploy"].append(new_deploy) + self.data.update(deploy_data) + else: + deploy_data.append(new_deploy) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + def query(self, from_release, to_release): + super().query(from_release, to_release) + for deploy in self.data.get("deploy", []): + if deploy.get("from_release") == from_release and deploy.get("to_release") == to_release: + return deploy + return None + + def query_all(self): + return self.data.get("deploy", []) + + def update(self, from_release, to_release, reboot_required, state): + super().update(from_release, to_release, reboot_required, state) + deploy = self.query(from_release, to_release) + if not deploy: + raise DeployDoNotExist("Error to update. Deploy do not exist.") + deploy_data = { + "deploy": [] + } + deploy_data["deploy"].append({ + "from_release": from_release, + "to_release": to_release, + "reboot_required": reboot_required, + "state": state + }) + self.data.update(deploy_data) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + def delete(self, from_release, to_release): + super().delete(from_release, to_release) + deploy = self.query(from_release, to_release) + if not deploy: + raise DeployDoNotExist("Error to delete. Deploy do not exist.") + self.data.get("deploy").remove(deploy) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + +class DeployHostHandler(DeployHosts): + + def __init__(self): + super().__init__() + self.data = get_software_filesystem_data() + + def create(self, hostname, software_release, target_release, state=None): + super().create(hostname, software_release, target_release, state) + deploy = self.query(hostname, software_release, target_release) + if deploy: + raise DeployAlreadyExist("Error to create. Deploy host already exist.") + + new_deploy_host = { + "hostname": hostname, + "software_release": software_release, + "target_release": target_release, + "state": state + } + + deploy_data = self.data.get("deploy_host", []) + if not deploy_data: + deploy_data = { + "deploy_host": [] + } + deploy_data["deploy_host"].append(new_deploy_host) + self.data.update(deploy_data) + else: + deploy_data.append(new_deploy_host) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + def query(self, hostname, software_release, target_release): + super().query(hostname, software_release, target_release) + for deploy in self.data.get("deploy_host", []): + if (deploy.get("hostname") == hostname and deploy.get("software_release") == software_release + and deploy.get("target_release") == target_release): + return deploy + return None + + def query_by_hostname(self, hostname): + super().query_by_hostname(hostname) + for deploy in self.data.get("deploy_host", []): + if deploy.get("hostname") == hostname: + return deploy + return None + + def query_all(self): + return self.data.get("deploy_host", []) + + def update(self, hostname, software_release, target_release, state): + super().update(hostname, software_release, target_release, state) + deploy = self.query(hostname, software_release, target_release) + if not deploy: + raise Exception("Error to update. Deploy host do not exist.") + + index = self.data.get("deploy_host", []).index(deploy) + self.data["deploy_host"][index].update({ + "hostname": hostname, + "software_release": software_release, + "target_release": target_release, + "state": state + }) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + def delete_all(self): + self.data.get("deploy_host").clear() + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + def delete_by_hostname(self, hostname): + super().delete_by_hostname(hostname) + deploy = self.query_by_hostname(hostname) + if not deploy: + raise DeployDoNotExist("Error to delete. Deploy host do not exist.") + self.data.get("deploy_host").remove(deploy) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) + + def delete(self, hostname, software_release, target_release): + super().delete(hostname, software_release, target_release) + deploy = self.query(hostname, software_release, target_release) + if not deploy: + raise DeployDoNotExist("Error to delete. Deploy host do not exist.") + self.data.get("deploy_host").remove(deploy) + save_to_json_file(constants.SOFTWARE_JSON_FILE, self.data) diff --git a/software/software/utils.py b/software/software/utils.py index 25da6177..0523c8eb 100644 --- a/software/software/utils.py +++ b/software/software/utils.py @@ -269,6 +269,10 @@ def load_from_json_file(file): except FileNotFoundError: LOG.error("File %s not found", file) return None + # Avoid error to read an empty file + except ValueError: + return {} + except Exception as e: LOG.error("Problem reading from %s: %s", file, e) return None @@ -289,6 +293,13 @@ def check_state(state, states): def check_instances(params: list, instance): for p in params: if not isinstance(p, instance): - msg = "Param %s must be type: %s" % (p, instance) + msg = "Param with value %s must be type: %s" % (p, instance) LOG.exception(msg) raise ValueError(msg) + +def get_software_filesystem_data(): + if os.path.exists(constants.SOFTWARE_JSON_FILE): + data = load_from_json_file(constants.SOFTWARE_JSON_FILE) + else: + data = {} + return data