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
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):
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
"""
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')
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()
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,
if credentials is None:
raise N2VCBadArgumentsException(message='credentials are mandatory', bad_args=['credentials'])
- if 'hostname' in credentials:
+ if credentials.get('hostname'):
hostname = credentials['hostname']
else:
raise N2VCBadArgumentsException(message='hostname is mandatory', bad_args=['credentials.hostname'])
- if 'username' in credentials:
+ if credentials.get('username'):
username = credentials['username']
else:
raise N2VCBadArgumentsException(message='username is mandatory', bad_args=['credentials.username'])
# 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,
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))
raise e
# return public key if exists
- return output
+ return output["pubkey"] if "pubkey" in output else output
async def add_relation(
self,
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,
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,
:return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
"""
+ # TODO: Enforce the Juju 50-character application limit
+
# split namespace components
_, _, vnf_id, vdu_id, vdu_count = self._get_namespace_components(namespace=namespace)
if vnf_id is None or len(vnf_id) == 0:
vnf_id = ''
else:
- vnf_id = 'vnf-' + vnf_id
+ # Shorten the vnf_id to its last twelve characters
+ vnf_id = 'vnf-' + vnf_id[-12:]
if vdu_id is None or len(vdu_id) == 0:
vdu_id = ''
else:
- vdu_id = '-vdu-' + vdu_id
+ # Shorten the vdu_id to its last twelve characters
+ vdu_id = '-vdu-' + vdu_id[-12:]
if vdu_count is None or len(vdu_count) == 0:
vdu_count = ''
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()
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,
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
)
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,
total_timeout: float = None
):
- # get juju model
- model = await self._juju_get_model(model_name=model_name)
-
# get the application
application = await self._juju_get_application(model_name=model_name, application_name=application_name)
)
# check if 'verify-ssh-credentials' action exists
- unit = application.units[0]
+ # unit = application.units[0]
actions = await application.get_actions()
if 'verify-ssh-credentials' not in actions:
msg = 'Action verify-ssh-credentials does not exist in application {}'.format(application_name)
+ self.debug(msg=msg)
return False
# execute verify-credentials
model = await self._juju_get_model(model_name=model_name)
uuid = model.info.uuid
- self.debug('disconnecting model {}...'.format(model_name))
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):
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