X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Fn2vc_juju_conn.py;h=40f46f1e3fd01c8775b243cca37d381ebb42b4b2;hp=4f0ee37d368a44aa66e385b172485d4df2889b1f;hb=d14e4213c439940a73225a7e12062465e31f371b;hpb=bc269eb13d444b0e382a38a710e13abb87f5c342 diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index 4f0ee37..40f46f1 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -32,7 +32,7 @@ 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 + N2VCExecutionException, N2VCInvalidCertificate, N2VCNotFound from n2vc.juju_observer import JujuModelObserver from juju.controller import Controller @@ -168,7 +168,8 @@ class N2VCJujuConnector(N2VCConnector): else: self.apt_mirror = None - self.log.debug('Arguments have been checked') + self.cloud = vca_config.get('cloud') + # self.log.debug('Arguments have been checked') # juju data self.controller = None # it will be filled when connect to juju @@ -337,7 +338,8 @@ class N2VCJujuConnector(N2VCConnector): artifact_path: str, db_dict: dict, progress_timeout: float = None, - total_timeout: float = None + total_timeout: float = None, + config: dict = None, ): self.log.info('Installing configuration sw on ee_id: {}, artifact path: {}, db_dict: {}' @@ -385,7 +387,8 @@ class N2VCJujuConnector(N2VCConnector): machine_id=machine_id, db_dict=db_dict, progress_timeout=progress_timeout, - total_timeout=total_timeout + total_timeout=total_timeout, + config=config ) except Exception as e: raise N2VCException(message='Error desploying charm into ee={} : {}'.format(ee_id, e)) @@ -437,7 +440,7 @@ class N2VCJujuConnector(N2VCConnector): total_timeout=total_timeout ) except Exception as e: - self.log.info('Cannot execute action generate-ssh-key: {}\nContinuing...'.format(e)) + self.log.info('Skipping exception while executing action generate-ssh-key: {}'.format(e)) # execute action: get-ssh-public-key try: @@ -452,7 +455,7 @@ class N2VCJujuConnector(N2VCConnector): except Exception as e: msg = 'Cannot execute action get-ssh-public-key: {}\n'.format(e) self.log.info(msg) - raise e + raise N2VCException(msg) # return public key if exists return output["pubkey"] if "pubkey" in output else output @@ -466,7 +469,7 @@ class N2VCJujuConnector(N2VCConnector): ): self.log.debug('adding new relation between {} and {}, endpoints: {}, {}' - .format(ee_id_1, ee_id_2, endpoint_1, endpoint_2)) + .format(ee_id_1, ee_id_2, endpoint_1, endpoint_2)) # check arguments if not ee_id_1: @@ -509,7 +512,7 @@ class N2VCJujuConnector(N2VCConnector): relation_2=endpoint_2 ) except Exception as e: - message = 'Error adding relation between {} and {}'.format(ee_id_1, ee_id_2) + message = 'Error adding relation between {} and {}: {}'.format(ee_id_1, ee_id_2, e) self.log.error(message) raise N2VCException(message=message) @@ -553,6 +556,8 @@ class N2VCJujuConnector(N2VCConnector): model_name=ns_id, total_timeout=total_timeout ) + except N2VCNotFound: + raise except Exception as e: raise N2VCException(message='Error deleting namespace {} : {}'.format(namespace, e)) else: @@ -585,15 +590,15 @@ class N2VCJujuConnector(N2VCConnector): .format(ee_id, application_name, e)) # destroy the machine - try: - await self._juju_destroy_machine( - model_name=model_name, - machine_id=machine_id, - total_timeout=total_timeout - ) - except Exception as e: - raise N2VCException(message='Error deleting execution environment {} (machine {}) : {}' - .format(ee_id, machine_id, e)) + # try: + # await self._juju_destroy_machine( + # model_name=model_name, + # machine_id=machine_id, + # total_timeout=total_timeout + # ) + # except Exception as e: + # raise N2VCException(message='Error deleting execution environment {} (machine {}) : {}' + # .format(ee_id, machine_id, e)) self.log.info('Execution environment {} deleted'.format(ee_id)) @@ -699,6 +704,8 @@ class N2VCJujuConnector(N2VCConnector): update_dict=update_dict, fail_on_empty=True ) + except asyncio.CancelledError: + raise except Exception as e: self.log.error('Error writing ee_id to database: {}'.format(e)) @@ -953,7 +960,8 @@ class N2VCJujuConnector(N2VCConnector): machine_id: str, db_dict: dict, progress_timeout: float = None, - total_timeout: float = None + total_timeout: float = None, + config: dict = None ) -> (Application, int): # get juju model and observer @@ -979,7 +987,8 @@ class N2VCJujuConnector(N2VCConnector): channel='stable', num_units=1, series=series, - to=machine_id + to=machine_id, + config=config ) # register application with observer @@ -1024,7 +1033,10 @@ class N2VCJujuConnector(N2VCConnector): application = await self._juju_get_application(model_name=model_name, application_name=application_name) - unit = application.units[0] + unit = None + for u in application.units: + if await u.is_leader_from_status(): + unit = u if unit is not None: actions = await application.get_actions() if action_name in actions: @@ -1103,6 +1115,8 @@ class N2VCJujuConnector(N2VCConnector): ) self.log.debug('Result: {}, output: {}'.format(ok, output)) return True + except asyncio.CancelledError: + raise except Exception as e: self.log.debug('Error executing verify-ssh-credentials: {}. Retrying...'.format(e)) await asyncio.sleep(retry_timeout) @@ -1166,7 +1180,8 @@ class N2VCJujuConnector(N2VCConnector): model = await self.controller.add_model( model_name=model_name, - config=config_dict + config=config_dict, + cloud_name=self.cloud, ) self.log.info('New model created, name={}'.format(model_name)) else: @@ -1223,9 +1238,11 @@ class N2VCJujuConnector(N2VCConnector): # get juju model and observer model = await self._juju_get_model(model_name=model_name) + observer = self.juju_observers[model_name] application = model.applications.get(application_name) if application: + observer.unregister_application(application_name) await application.destroy() else: self.log.debug('Application not found: {}'.format(application_name)) @@ -1244,20 +1261,28 @@ class N2VCJujuConnector(N2VCConnector): # get juju model and observer model = await self._juju_get_model(model_name=model_name) + observer = self.juju_observers[model_name] machines = await model.get_machines() if machine_id in machines: machine = model.machines[machine_id] - await machine.destroy(force=True) - # max timeout - end = time.time() + total_timeout - # wait for machine removal - machines = await model.get_machines() - while machine_id in machines and time.time() < end: - self.log.debug('Waiting for machine {} is destroyed'.format(machine_id)) - await asyncio.sleep(0.5) + observer.unregister_machine(machine_id) + # TODO: change this by machine.is_manual when this is upstreamed: https://github.com/juju/python-libjuju/pull/396 + if "instance-id" in machine.safe_data and machine.safe_data[ + "instance-id" + ].startswith("manual:"): + self.log.debug("machine.destroy(force=True) started.") + await machine.destroy(force=True) + self.log.debug("machine.destroy(force=True) passed.") + # max timeout + end = time.time() + total_timeout + # wait for machine removal machines = await model.get_machines() - self.log.debug('Machine destroyed: {}'.format(machine_id)) + while machine_id in machines and time.time() < end: + self.log.debug("Waiting for machine {} is destroyed".format(machine_id)) + await asyncio.sleep(0.5) + machines = await model.get_machines() + self.log.debug("Machine destroyed: {}".format(machine_id)) else: self.log.debug('Machine not found: {}'.format(machine_id)) @@ -1271,40 +1296,63 @@ class N2VCJujuConnector(N2VCConnector): if total_timeout is None: total_timeout = 3600 + end = time.time() + total_timeout model = await self._juju_get_model(model_name=model_name) + + if not model: + raise N2VCNotFound( + message="Model {} does not exist".format(model_name) + ) + uuid = model.info.uuid + # destroy applications + for application_name in model.applications: + try: + await self._juju_destroy_application(model_name=model_name, application_name=application_name) + except Exception as e: + self.log.error( + "Error destroying application {} in model {}: {}".format( + application_name, + model_name, + e + ) + ) + # 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 asyncio.CancelledError: + raise 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.log.debug('destroying model {}...'.format(model_name)) await self.controller.destroy_model(uuid) - self.log.debug('model destroy requested {}'.format(model_name)) + # self.log.debug('model destroy requested {}'.format(model_name)) # wait for model is completely destroyed - end = time.time() + total_timeout + self.log.debug('Waiting for model {} to be destroyed...'.format(model_name)) + last_exception = '' while time.time() < end: - self.log.debug('Waiting for model is destroyed...') try: # await self.controller.get_model(uuid) models = await self.controller.list_models() if model_name not in models: self.log.debug('The model {} ({}) was destroyed'.format(model_name, uuid)) return + except asyncio.CancelledError: + raise except Exception as e: - pass - await asyncio.sleep(1.0) + last_exception = e + await asyncio.sleep(5) + raise N2VCException("Timeout waiting for model {} to be destroyed {}".format(model_name, last_exception)) async def _juju_login(self): """Connect to juju controller @@ -1327,8 +1375,8 @@ class N2VCJujuConnector(N2VCConnector): try: self._connecting = True self.log.info( - 'connecting to juju controller: {} {}:{} ca_cert: {}' - .format(self.url, self.username, self.secret, '\n'+self.ca_cert if self.ca_cert else 'None')) + 'connecting to juju controller: {} {}:{}{}' + .format(self.url, self.username, self.secret[:8] + '...', ' with ca_cert' if self.ca_cert else '')) # Create controller object self.controller = Controller(loop=self.loop)