X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=n2vc%2Fn2vc_conn.py;h=68e8c14834da5ed8c5761ff1141a3c148abe6dae;hb=HEAD;hp=c0bb558a3a05e0eac260dcc5971fc280fde8ef31;hpb=f52cb7cfeb4e24febe7c66af3d5bb275a50d7f99;p=osm%2FN2VC.git diff --git a/n2vc/n2vc_conn.py b/n2vc/n2vc_conn.py index c0bb558..01d7df8 100644 --- a/n2vc/n2vc_conn.py +++ b/n2vc/n2vc_conn.py @@ -23,8 +23,8 @@ import abc import asyncio -from enum import Enum from http import HTTPStatus +from shlex import quote import os import shlex import subprocess @@ -35,14 +35,7 @@ from osm_common.dbmongo import DbException import yaml from n2vc.loggable import Loggable - - -class N2VCDeploymentStatus(Enum): - PENDING = "pending" - RUNNING = "running" - COMPLETED = "completed" - FAILED = "failed" - UNKNOWN = "unknown" +from n2vc.utils import JujuStatusToOSM, N2VCDeploymentStatus class N2VCConnector(abc.ABC, Loggable): @@ -62,11 +55,8 @@ class N2VCConnector(abc.ABC, Loggable): db: object, fs: object, log: object, - loop: object, - url: str, - username: str, - vca_config: dict, on_update_db=None, + **kwargs, ): """Initialize N2VC abstract connector. It defines de API for VCA connectors @@ -74,15 +64,6 @@ class N2VCConnector(abc.ABC, Loggable): :param object fs: FileSystem object managing the package artifacts (repo common FsBase) :param object log: the logging object to log to - :param object loop: the loop to use for asyncio (default current thread loop) - :param str url: a string that how to connect to the VCA (if needed, IP and port - can be obtained from there) - :param str username: the username to authenticate with VCA - :param dict vca_config: Additional parameters for the specific VCA. For example, - for juju it will contain: - secret: The password to authenticate with - public_key: The contents of the juju public SSH key - ca_cert str: The CA certificate used to authenticate :param on_update_db: callback called when n2vc connector updates database. Received arguments: table: e.g. "nsrs" @@ -100,32 +81,14 @@ class N2VCConnector(abc.ABC, Loggable): if fs is None: raise N2VCBadArgumentsException("Argument fs is mandatory", ["fs"]) - self.log.info( - "url={}, username={}, vca_config={}".format( - url, - username, - { - k: v - for k, v in vca_config.items() - if k - not in ("host", "port", "user", "secret", "public_key", "ca_cert") - }, - ) - ) - # store arguments into self self.db = db self.fs = fs - self.loop = loop or asyncio.get_event_loop() - self.url = url - self.username = username - self.vca_config = vca_config self.on_update_db = on_update_db # generate private/public key-pair self.private_key_path = None self.public_key_path = None - self.get_public_key() @abc.abstractmethod async def get_status(self, namespace: str, yaml_format: bool = True): @@ -150,22 +113,30 @@ class N2VCConnector(abc.ABC, Loggable): # Find the path where we expect our key lives (~/.ssh) homedir = os.environ.get("HOME") if not homedir: - self.warning("No HOME environment variable, using /tmp") + self.log.warning("No HOME environment variable, using /tmp") homedir = "/tmp" sshdir = "{}/.ssh".format(homedir) + sshdir = os.path.realpath(os.path.normpath(os.path.abspath(sshdir))) if not os.path.exists(sshdir): os.mkdir(sshdir) self.private_key_path = "{}/id_n2vc_rsa".format(sshdir) + self.private_key_path = os.path.realpath( + os.path.normpath(os.path.abspath(self.private_key_path)) + ) self.public_key_path = "{}.pub".format(self.private_key_path) + self.public_key_path = os.path.realpath( + os.path.normpath(os.path.abspath(self.public_key_path)) + ) # If we don't have a key generated, then we have to generate it using ssh-keygen if not os.path.exists(self.private_key_path): - cmd = "ssh-keygen -t {} -b {} -N '' -f {}".format( - "rsa", "4096", self.private_key_path + command = "ssh-keygen -t {} -b {} -N '' -f {}".format( + "rsa", "4096", quote(self.private_key_path) ) # run command with arguments - subprocess.check_output(shlex.split(cmd)) + args = shlex.split(command) + subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Read the public key. Only one public key (one line) in the file with open(self.public_key_path, "r") as file: @@ -181,7 +152,7 @@ class N2VCConnector(abc.ABC, Loggable): reuse_ee_id: str = None, progress_timeout: float = None, total_timeout: float = None, - ) -> (str, dict): + ) -> tuple[str, dict]: """Create an Execution Environment. Returns when it is created or raises an exception on failing @@ -253,6 +224,38 @@ class N2VCConnector(abc.ABC, Loggable): :param float total_timeout: """ + @abc.abstractmethod + async def install_k8s_proxy_charm( + self, + charm_name: str, + namespace: str, + artifact_path: str, + db_dict: dict, + progress_timeout: float = None, + total_timeout: float = None, + config: dict = None, + ) -> str: + """ + Install a k8s proxy charm + + :param charm_name: Name of the charm being deployed + :param namespace: collection of all the uuids related to the charm. + :param str artifact_path: where to locate the artifacts (parent folder) using + the self.fs + the final artifact path will be a combination of this artifact_path and + additional string from the config_dict (e.g. charm name) + :param dict db_dict: where to write into database when the status changes. + It contains a dict with + {collection: , filter: {}, path: }, + e.g. {collection: "nsrs", filter: + {_id: , path: "_admin.deployed.VCA.3"} + :param float progress_timeout: + :param float total_timeout: + :param config: Dictionary with additional configuration + + :returns ee_id: execution environment id. + """ + @abc.abstractmethod async def get_ee_ssh_public__key( self, @@ -297,14 +300,12 @@ class N2VCConnector(abc.ABC, Loggable): # TODO @abc.abstractmethod async def remove_relation(self): - """ - """ + """ """ # TODO @abc.abstractmethod async def deregister_execution_environments(self): - """ - """ + """ """ @abc.abstractmethod async def delete_namespace( @@ -336,6 +337,28 @@ class N2VCConnector(abc.ABC, Loggable): :param float total_timeout: """ + @abc.abstractmethod + async def upgrade_charm( + self, + ee_id: str = None, + path: str = None, + charm_id: str = None, + charm_type: str = None, + timeout: float = None, + ) -> str: + """This method upgrade charms in VNFs + + Args: + ee_id: Execution environment id + path: Local path to the charm + charm_id: charm-id + charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm + timeout: (Float) Timeout for the ns update operation + + Returns: + The output of the update operation if status equals to "completed" + """ + @abc.abstractmethod async def exec_primitive( self, @@ -376,7 +399,9 @@ class N2VCConnector(abc.ABC, Loggable): #################################################################################### """ - def _get_namespace_components(self, namespace: str) -> (str, str, str, str, str): + def _get_namespace_components( + self, namespace: str + ) -> tuple[str, str, str, str, str]: """ Split namespace components @@ -419,7 +444,18 @@ class N2VCConnector(abc.ABC, Loggable): detailed_status: str, vca_status: str, entity_type: str, + vca_id: str = None, ): + """ + Write application status to database + + :param: db_dict: DB dictionary + :param: status: Status of the application + :param: detailed_status: Detailed status + :param: vca_status: VCA status + :param: entity_type: Entity type ("application", "machine, and "action") + :param: vca_id: Id of the VCA. If None, the default VCA will be used. + """ if not db_dict: self.log.debug("No db_dict => No database write") return @@ -428,7 +464,6 @@ class N2VCConnector(abc.ABC, Loggable): # .format(str(status.value), detailed_status, vca_status, entity_type)) try: - the_table = db_dict["collection"] the_filter = db_dict["filter"] the_path = db_dict["path"] @@ -453,10 +488,12 @@ class N2VCConnector(abc.ABC, Loggable): if self.on_update_db: if asyncio.iscoroutinefunction(self.on_update_db): await self.on_update_db( - the_table, the_filter, the_path, update_dict + the_table, the_filter, the_path, update_dict, vca_id=vca_id ) else: - self.on_update_db(the_table, the_filter, the_path, update_dict) + self.on_update_db( + the_table, the_filter, the_path, update_dict, vca_id=vca_id + ) except DbException as e: if e.http_code == HTTPStatus.NOT_FOUND: @@ -468,35 +505,11 @@ class N2VCConnector(abc.ABC, Loggable): else: self.log.info("Exception writing status to database: {}".format(e)) - -def juju_status_2_osm_status(statustype: str, status: str) -> N2VCDeploymentStatus: - if statustype == "application" or statustype == "unit": - if status in ["waiting", "maintenance"]: - return N2VCDeploymentStatus.RUNNING - if status in ["error"]: - return N2VCDeploymentStatus.FAILED - elif status in ["active"]: - return N2VCDeploymentStatus.COMPLETED - elif status in ["blocked"]: - return N2VCDeploymentStatus.RUNNING - else: + def osm_status(self, entity_type: str, status: str) -> N2VCDeploymentStatus: + if status not in JujuStatusToOSM[entity_type]: + self.log.warning("Status {} not found in JujuStatusToOSM.".format(status)) return N2VCDeploymentStatus.UNKNOWN - elif statustype == "action": - if status in ["running"]: - return N2VCDeploymentStatus.RUNNING - elif status in ["completed"]: - return N2VCDeploymentStatus.COMPLETED - else: - return N2VCDeploymentStatus.UNKNOWN - elif statustype == "machine": - if status in ["pending"]: - return N2VCDeploymentStatus.PENDING - elif status in ["started"]: - return N2VCDeploymentStatus.COMPLETED - else: - return N2VCDeploymentStatus.UNKNOWN - - return N2VCDeploymentStatus.FAILED + return JujuStatusToOSM[entity_type][status] def obj_to_yaml(obj: object) -> str: @@ -518,4 +531,4 @@ def obj_to_dict(obj: object) -> dict: # convert obj to yaml yaml_text = obj_to_yaml(obj) # parse to dict - return yaml.load(yaml_text, Loader=yaml.Loader) + return yaml.load(yaml_text, Loader=yaml.SafeLoader)