From ac4e0dec95b3f18b57ee176136cb9885a8f3e2e1 Mon Sep 17 00:00:00 2001 From: quilesj Date: Wed, 27 Nov 2019 16:12:02 +0000 Subject: [PATCH] Fix native charms for feature 7928 Change-Id: Iba37a42152b2674c80bd887b2761827ffa19e4a8 Signed-off-by: quilesj --- n2vc/exceptions.py | 10 ---- n2vc/juju_observer.py | 9 +++- n2vc/loggable.py | 2 +- n2vc/n2vc_conn.py | 4 +- n2vc/n2vc_juju_conn.py | 106 ++++++++++++++++++++++++++++++++--------- 5 files changed, 95 insertions(+), 36 deletions(-) diff --git a/n2vc/exceptions.py b/n2vc/exceptions.py index 35962b2..4b83e3f 100644 --- a/n2vc/exceptions.py +++ b/n2vc/exceptions.py @@ -40,14 +40,6 @@ class AuthenticationFailed(Exception): """The authentication for the specified user failed.""" -class InvalidCACertificate(Exception): - """The CA Certificate is not valid.""" - - -class NotImplemented(Exception): - """The method is not implemented.""" - - class N2VCException(Exception): """ N2VC exception base class @@ -115,7 +107,6 @@ class N2VCExecutionException(N2VCException): def __str__(self): return '<{}> Error executing primitive {} failed: {}'.format(type(self), self.primitive_name, super().__str__()) - class N2VCInvalidCertificate(N2VCException): """ Invalid certificate @@ -126,4 +117,3 @@ class N2VCInvalidCertificate(N2VCException): def __str__(self): return '<{}> Invalid certificate: {}'.format(type(self), super().__str__()) - diff --git a/n2vc/juju_observer.py b/n2vc/juju_observer.py index ac40f34..f4102a4 100644 --- a/n2vc/juju_observer.py +++ b/n2vc/juju_observer.py @@ -52,7 +52,12 @@ class JujuModelObserver(ModelObserver): self.actions = dict() def register_machine(self, machine: Machine, db_dict: dict): - entity_id = machine.entity_id + try: + entity_id = machine.entity_id + except: + # no entity_id aatribute, try machine attribute + entity_id = machine.machine + self.n2vc.debug(msg='Registering machine for changes notifications: {}'.format(entity_id)) entity = _Entity(entity_id=entity_id, entity_type='machine', obj=machine, db_dict=db_dict) self.machines[entity_id] = entity @@ -65,6 +70,7 @@ class JujuModelObserver(ModelObserver): def register_application(self, application: Application, db_dict: dict): entity_id = application.entity_id + self.n2vc.debug(msg='Registering application for changes notifications: {}'.format(entity_id)) entity = _Entity(entity_id=entity_id, entity_type='application', obj=application, db_dict=db_dict) self.applications[entity_id] = entity @@ -77,6 +83,7 @@ class JujuModelObserver(ModelObserver): def register_action(self, action: Action, db_dict: dict): entity_id = action.entity_id + self.n2vc.debug(msg='Registering action for changes notifications: {}'.format(entity_id)) entity = _Entity(entity_id=entity_id, entity_type='action', obj=action, db_dict=db_dict) self.actions[entity_id] = entity diff --git a/n2vc/loggable.py b/n2vc/loggable.py index 40efa24..87a645d 100644 --- a/n2vc/loggable.py +++ b/n2vc/loggable.py @@ -126,7 +126,7 @@ class Loggable: # datetime dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') dt = dt + time_str - dt = time_str # logger already shows datetime + # dt = time_str # logger already shows datetime # current thread if include_thread: diff --git a/n2vc/n2vc_conn.py b/n2vc/n2vc_conn.py index 97b6188..d3aaf35 100644 --- a/n2vc/n2vc_conn.py +++ b/n2vc/n2vc_conn.py @@ -106,6 +106,8 @@ class N2VCConnector(abc.ABC, Loggable): 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 @@ -116,7 +118,7 @@ class N2VCConnector(abc.ABC, Loggable): """ # TODO: review which public key - async def get_public_key(self) -> str: + def get_public_key(self) -> str: """Get the VCA ssh-public-key Returns the SSH public key from local mahine, to be injected into virtual machines to diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index 2d2fbdb..aba88ee 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -39,6 +39,9 @@ from juju.model import Model from juju.application import Application from juju.action import Action from juju.machine import Machine +from juju.client import client + +from n2vc.provisioner import SSHProvisioner class N2VCJujuConnector(N2VCConnector): @@ -58,8 +61,7 @@ class N2VCJujuConnector(N2VCConnector): url: str = '127.0.0.1:17070', username: str = 'admin', vca_config: dict = None, - on_update_db=None, - api_proxy=None + on_update_db=None ): """Initialize juju N2VC connector """ @@ -148,8 +150,9 @@ class N2VCJujuConnector(N2VCConnector): if self.ca_cert: self.ca_cert = base64_to_cacert(vca_config['ca_cert']) - if api_proxy: - self.api_proxy = api_proxy + if 'api_proxy' in vca_config: + self.api_proxy = vca_config['api_proxy'] + self.debug('api_proxy for native charms configured: {}'.format(self.api_proxy)) else: self.warning('api_proxy is not configured. Support for native charms is disabled') @@ -286,7 +289,7 @@ class N2VCJujuConnector(N2VCConnector): # register machine on juju try: - machine = await self._juju_provision_machine( + machine_id = await self._juju_provision_machine( model_name=model_name, hostname=hostname, username=username, @@ -298,13 +301,14 @@ class N2VCJujuConnector(N2VCConnector): except Exception as e: self.error('Error registering machine: {}'.format(e)) raise N2VCException(message='Error registering machine on juju: {}'.format(e)) - self.info('Machine registered') + + self.info('Machine registered: {}'.format(machine_id)) # id for the execution environment ee_id = N2VCJujuConnector._build_ee_id( model_name=model_name, application_name=application_name, - machine_id=str(machine.entity_id) + machine_id=str(machine_id) ) self.info('Execution environment registered. ee_id: {}'.format(ee_id)) @@ -808,9 +812,14 @@ class N2VCJujuConnector(N2VCConnector): db_dict: dict = None, progress_timeout: float = None, total_timeout: float = None - ) -> Machine: + ) -> str: + + if not self.api_proxy: + msg = 'Cannot provision machine: api_proxy is not defined' + self.error(msg=msg) + raise N2VCException(message=msg) - self.debug('provisioning machine. model: {}, hostname: {}'.format(model_name, hostname)) + self.debug('provisioning machine. model: {}, hostname: {}, username: {}'.format(model_name, hostname, username)) if not self._authenticated: await self._juju_login() @@ -819,30 +828,79 @@ class N2VCJujuConnector(N2VCConnector): model = await self._juju_get_model(model_name=model_name) observer = self.juju_observers[model_name] - spec = 'ssh:{}@{}:{}'.format(username, hostname, private_key_path) - self.debug('provisioning machine {}'.format(spec)) + # TODO check if machine is already provisioned + machine_list = await model.get_machines() + + provisioner = SSHProvisioner( + host=hostname, + user=username, + private_key_path=private_key_path, + log=self.log + ) + + params = None try: - machine = await model.add_machine(spec=spec) - except Exception as e: - import sys - import traceback - traceback.print_exc(file=sys.stdout) - print('-' * 60) - raise e + params = provisioner.provision_machine() + except Exception as ex: + msg = "Exception provisioning machine: {}".format(ex) + self.log.error(msg) + raise N2VCException(message=msg) + + params.jobs = ['JobHostUnits'] + + connection = model.connection() + + # Submit the request. + self.debug("Adding machine to model") + client_facade = client.ClientFacade.from_connection(connection) + results = await client_facade.AddMachines(params=[params]) + error = results.machines[0].error + if error: + msg = "Error adding machine: {}}".format(error.message) + self.error(msg=msg) + raise ValueError(msg) + + machine_id = results.machines[0].machine + + # Need to run this after AddMachines has been called, + # as we need the machine_id + self.debug("Installing Juju agent into machine {}".format(machine_id)) + asyncio.ensure_future(provisioner.install_agent( + connection=connection, + nonce=params.nonce, + machine_id=machine_id, + api=self.api_proxy, + )) + + # wait for machine in model (now, machine is not yet in model, so we must wait for it) + machine = None + for i in range(10): + machine_list = await model.get_machines() + if machine_id in machine_list: + self.debug('Machine {} found in model!'.format(machine_id)) + machine = model.machines.get(machine_id) + break + await asyncio.sleep(2) + + if machine is None: + msg = 'Machine {} not found in model'.format(machine_id) + self.error(msg=msg) + raise Exception(msg) # register machine with observer observer.register_machine(machine=machine, db_dict=db_dict) # wait for machine creation - self.debug('waiting for provision completed... {}'.format(machine.entity_id)) + self.debug('waiting for provision finishes... {}'.format(machine_id)) await observer.wait_for_machine( - machine=machine, + machine_id=machine_id, progress_timeout=progress_timeout, total_timeout=total_timeout ) - self.debug("Machine provisioned {}".format(machine.entity_id)) - return machine + self.debug("Machine provisioned {}".format(machine_id)) + + return machine_id async def _juju_deploy_charm( self, @@ -870,12 +928,14 @@ class N2VCJujuConnector(N2VCConnector): self.debug('deploying application {} to machine {}, model {}' .format(application_name, machine_id, model_name)) self.debug('charm: {}'.format(charm_path)) + series = 'xenial' + # series = None application = await model.deploy( entity_url=charm_path, application_name=application_name, channel='stable', num_units=1, - series='xenial', + series=series, to=machine_id ) -- 2.17.1