X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Fn2vc_juju_conn.py;h=e0f1824c2cbd6746c465e9e804681577a0d9f196;hp=7c55af73045ceee4d43226b0bbdc37ad2b710051;hb=4d193dc97be946877fc9b033e5d267b103a7cc45;hpb=9ae8fa51d23e2373a7e25187b16401341051575e diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index 7c55af7..e0f1824 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -29,6 +29,7 @@ import binascii import re from n2vc.n2vc_conn import N2VCConnector +from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml from n2vc.exceptions \ import N2VCBadArgumentsException, N2VCException, N2VCConnectionException, \ N2VCExecutionException, N2VCInvalidCertificate @@ -40,6 +41,7 @@ from juju.application import Application from juju.action import Action from juju.machine import Machine from juju.client import client +from juju.errors import JujuAPIError from n2vc.provisioner import SSHProvisioner @@ -156,6 +158,16 @@ class N2VCJujuConnector(N2VCConnector): else: self.warning('api_proxy is not configured. Support for native charms is disabled') + if 'enable_os_upgrade' in vca_config: + self.enable_os_upgrade = vca_config['enable_os_upgrade'] + else: + self.enable_os_upgrade = True + + if 'apt_mirror' in vca_config: + self.apt_mirror = vca_config['apt_mirror'] + else: + self.apt_mirror = None + self.debug('Arguments have been checked') # juju data @@ -171,8 +183,9 @@ class N2VCJujuConnector(N2VCConnector): self.info('N2VC juju connector initialized') - async def get_status(self, namespace: str): - self.info('Getting NS status. namespace: {}'.format(namespace)) + async def get_status(self, namespace: str, yaml_format: bool = True): + + # self.info('Getting NS status. namespace: {}'.format(namespace)) if not self._authenticated: await self._juju_login() @@ -190,7 +203,10 @@ class N2VCJujuConnector(N2VCConnector): status = await model.get_status() - return status + if yaml_format: + return obj_to_yaml(status) + else: + return obj_to_dict(status) async def create_execution_environment( self, @@ -452,10 +468,28 @@ class N2VCJujuConnector(N2VCConnector): self.debug('adding new relation between {} and {}, endpoints: {}, {}' .format(ee_id_1, ee_id_2, endpoint_1, endpoint_2)) + # check arguments + if not ee_id_1: + message = 'EE 1 is mandatory' + self.error(message) + raise N2VCBadArgumentsException(message=message, bad_args=['ee_id_1']) + if not ee_id_2: + message = 'EE 2 is mandatory' + self.error(message) + raise N2VCBadArgumentsException(message=message, bad_args=['ee_id_2']) + if not endpoint_1: + message = 'endpoint 1 is mandatory' + self.error(message) + raise N2VCBadArgumentsException(message=message, bad_args=['endpoint_1']) + if not endpoint_2: + message = 'endpoint 2 is mandatory' + self.error(message) + raise N2VCBadArgumentsException(message=message, bad_args=['endpoint_2']) + if not self._authenticated: await self._juju_login() - # get model, application and machines + # get the model, the applications and the machines from the ee_id's model_1, app_1, machine_1 = self._get_ee_id_components(ee_id_1) model_2, app_2, machine_2 = self._get_ee_id_components(ee_id_2) @@ -467,7 +501,13 @@ class N2VCJujuConnector(N2VCConnector): # add juju relations between two applications try: - self._juju_add_relation() + await self._juju_add_relation( + model_name=model_1, + application_name_1=app_1, + application_name_2=app_2, + relation_1=endpoint_1, + relation_2=endpoint_2 + ) except Exception as e: message = 'Error adding relation between {} and {}'.format(ee_id_1, ee_id_2) self.error(message) @@ -508,7 +548,6 @@ class N2VCJujuConnector(N2VCConnector): nsi_id, ns_id, vnf_id, vdu_id, vdu_count = self._get_namespace_components(namespace=namespace) if ns_id is not None: - self.debug('Deleting model {}'.format(ns_id)) try: await self._juju_destroy_model( model_name=ns_id, @@ -653,7 +692,7 @@ class N2VCJujuConnector(N2VCConnector): if not the_path[-1] == '.': the_path = the_path + '.' update_dict = {the_path + 'ee_id': ee_id} - self.debug('Writing ee_id to database: {}'.format(the_path)) + # self.debug('Writing ee_id to database: {}'.format(the_path)) self.db.set_one( table=the_table, q_filter=the_filter, @@ -985,18 +1024,16 @@ class N2VCJujuConnector(N2VCConnector): application = await self._juju_get_application(model_name=model_name, application_name=application_name) - self.debug('trying to execute action {}'.format(action_name)) unit = application.units[0] if unit is not None: actions = await application.get_actions() if action_name in actions: - self.debug('executing action {} with params {}'.format(action_name, kwargs)) + self.debug('executing action "{}" using params: {}'.format(action_name, kwargs)) action = await unit.run_action(action_name, **kwargs) # register action with observer observer.register_action(action=action, db_dict=db_dict) - self.debug(' waiting for action completed or error...') await observer.wait_for_action( action_id=action.entity_id, progress_timeout=progress_timeout, @@ -1091,6 +1128,7 @@ class N2VCJujuConnector(N2VCConnector): async def _juju_get_model(self, model_name: str) -> Model: """ Get a model object from juju controller + If the model does not exits, it creates it. :param str model_name: name of the model :returns Model: model obtained from juju controller or Exception @@ -1119,9 +1157,16 @@ class N2VCJujuConnector(N2VCConnector): if model_name not in model_list: self.info('Model {} does not exist. Creating new model...'.format(model_name)) + config_dict = {'authorized-keys': self.public_key} + if self.apt_mirror: + config_dict['apt-mirror'] = self.apt_mirror + if not self.enable_os_upgrade: + config_dict['enable-os-refresh-update'] = False + config_dict['enable-os-upgrade'] = False + model = await self.controller.add_model( model_name=model_name, - config={'authorized-keys': self.public_key} + config=config_dict ) self.info('New model created, name={}'.format(model_name)) else: @@ -1149,14 +1194,24 @@ class N2VCJujuConnector(N2VCConnector): relation_2: str ): - self.debug('adding relation') - # get juju model and observer model = await self._juju_get_model(model_name=model_name) r1 = '{}:{}'.format(application_name_1, relation_1) r2 = '{}:{}'.format(application_name_2, relation_2) - await model.add_relation(relation1=r1, relation2=r2) + + self.debug('adding relation: {} -> {}'.format(r1, r2)) + try: + await model.add_relation(relation1=r1, relation2=r2) + except JujuAPIError as e: + # If one of the applications in the relationship doesn't exist, or the relation has already been added, + # let the operation fail silently. + if 'not found' in e.message: + return + if 'already exists' in e.message: + return + # another execption, raise it + raise e async def _juju_destroy_application( self, @@ -1220,23 +1275,35 @@ class N2VCJujuConnector(N2VCConnector): model = await self._juju_get_model(model_name=model_name) uuid = model.info.uuid - self.debug('disconnecting model {}...'.format(model_name)) + # destroy machines + machines = await model.get_machines() + for machine_id in machines: + try: + await self._juju_destroy_machine(model_name=model_name, machine_id=machine_id) + except Exception as e: + # ignore exceptions destroying machine + pass + await self._juju_disconnect_model(model_name=model_name) self.juju_models[model_name] = None self.juju_observers[model_name] = None self.debug('destroying model {}...'.format(model_name)) await self.controller.destroy_model(uuid) + self.debug('model destroy requested {}'.format(model_name)) # wait for model is completely destroyed end = time.time() + total_timeout while time.time() < end: - self.debug('waiting for model is destroyed...') + self.debug('Waiting for model is destroyed...') try: - await self.controller.get_model(uuid) - except Exception: - self.debug('model destroyed') - return + # await self.controller.get_model(uuid) + models = await self.controller.list_models() + if model_name not in models: + self.debug('The model {} ({}) was destroyed'.format(model_name, uuid)) + return + except Exception as e: + pass await asyncio.sleep(1.0) async def _juju_login(self): @@ -1316,6 +1383,8 @@ class N2VCJujuConnector(N2VCConnector): await self.juju_models[model_name].disconnect() self.juju_models[model_name] = None self.juju_observers[model_name] = None + else: + self.warning('Cannot disconnect model: {}'.format(model_name)) def _create_juju_public_key(self): """Recreate the Juju public key on lcm container, if needed