class JujuApplicationExists(Exception):
"""The Application already exists."""
+class N2VCPrimitiveExecutionFailed(Exception):
+ """Something failed while attempting to execute a primitive."""
+
# Quiet the debug logging
logging.getLogger('websockets.protocol').setLevel(logging.INFO)
class VCAMonitor(ModelObserver):
"""Monitor state changes within the Juju Model."""
- callback = None
- callback_args = None
log = None
ns_name = None
- application_name = None
+ applications = {}
- def __init__(self, ns_name, application_name, callback, *args):
+ def __init__(self, ns_name):
self.log = logging.getLogger(__name__)
self.ns_name = ns_name
- self.application_name = application_name
- self.callback = callback
- self.callback_args = args
+
+ def AddApplication(self, application_name, callback, *callback_args):
+ if application_name not in self.applications:
+ self.applications[application_name] = {
+ 'callback': callback,
+ 'callback_args': callback_args
+ }
+
+ def RemoveApplication(self, application_name):
+ if application_name in self.applications:
+ del self.applications[application_name]
async def on_change(self, delta, old, new, model):
"""React to changes in the Juju model."""
if delta.entity == "unit":
+ # Ignore change events from other applications
+ if delta.data['application'] not in self.applications.keys():
+ return
+
try:
+
+ application_name = delta.data['application']
+
+ callback = self.applications[application_name]['callback']
+ callback_args = self.applications[application_name]['callback_args']
+
if old and new:
old_status = old.workload_status
new_status = new.workload_status
+
if old_status == new_status:
"""The workload status may fluctuate around certain events,
so wait until the status has stabilized before triggering
the callback."""
- if self.callback:
- self.callback(
+ if callback:
+ callback(
self.ns_name,
- self.application_name,
+ delta.data['application'],
new_status,
- *self.callback_args)
+ *callback_args)
+
+ if old and not new:
+ # This is a charm being removed
+ if callback:
+ callback(
+ self.ns_name,
+ delta.data['application'],
+ "removed",
+ *callback_args)
except Exception as e:
self.log.debug("[1] notify_callback exception {}".format(e))
+ elif delta.entity == "action":
+ # TODO: Decide how we want to notify the user of actions
+
+ # uuid = delta.data['id'] # The Action's unique id
+ # msg = delta.data['message'] # The output of the action
+ #
+ # if delta.data['status'] == "pending":
+ # # The action is queued
+ # pass
+ # elif delta.data['status'] == "completed""
+ # # The action was successful
+ # pass
+ # elif delta.data['status'] == "failed":
+ # # The action failed.
+ # pass
+ pass
########
# TODO
callback(model_name, application_name, status, *callback_args)
except Exception as e:
self.log.error("[0] notify_callback exception {}".format(e))
+ raise e
return True
# Public methods
:param dict params: A dictionary of runtime parameters
Examples::
{
- 'rw_mgmt_ip': '1.2.3.4'
+ 'rw_mgmt_ip': '1.2.3.4',
+ # Pass the initial-config-primitives section of the vnf or vdu
+ 'initial-config-primitives': {...}
}
:param dict machine_spec: A dictionary describing the machine to install to
Examples::
# service. In the meantime, we will always use the 'default' model.
model_name = 'default'
model = await self.get_model(model_name)
- # if model_name not in self.models:
- # self.log.debug("Getting model {}".format(model_name))
- # self.models[model_name] = await self.controller.get_model(model_name)
- # model = await self.CreateNetworkService(ns_name)
-
- ###################################################
- # Get the name of the charm and its configuration #
- ###################################################
- config_dict = vnfd['vnf-configuration']
- juju = config_dict['juju']
- charm = juju['charm']
- self.log.debug("Charm: {}".format(charm))
########################################
# Verify the application doesn't exist #
if app:
raise JujuApplicationExists("Can't deploy application \"{}\" to model \"{}\" because it already exists.".format(application_name, model))
- ############################################################
- # Create a monitor to watch for application status changes #
- ############################################################
+ ################################################################
+ # Register this application with the model-level event monitor #
+ ################################################################
if callback:
- self.log.debug("Setting monitor<->callback")
- self.monitors[application_name] = VCAMonitor(model_name, application_name, callback, *callback_args)
- model.add_observer(self.monitors[application_name])
-
+ self.monitors[model_name].AddApplication(
+ application_name,
+ callback,
+ *callback_args
+ )
########################################################
# Check for specific machine placement (native charms) #
rw_mgmt_ip = params['rw_mgmt_ip']
initial_config = self._get_config_from_dict(
- config_dict['initial-config-primitive'],
+ params['initial-config-primitive'],
{'<rw_mgmt_ip>': rw_mgmt_ip}
)
- self.log.debug("JujuApi: Deploying charm {} ({}) from {}".format(
- charm,
+ self.log.debug("JujuApi: Deploying charm ({}) from {}".format(
application_name,
charm_path,
to=to,
# Deploy the charm and apply the initial configuration #
########################################################
app = await model.deploy(
+ # We expect charm_path to be either the path to the charm on disk
+ # or in the format of cs:series/name
charm_path,
+ # This is the formatted, unique name for this charm
application_name=application_name,
+ # Proxy charms should use the current LTS. This will need to be
+ # changed for native charms.
series='xenial',
+ # Apply the initial 'config' primitive during deployment
config=initial_config,
+ # TBD: Where to deploy the charm to.
to=None,
)
+ # #######################################
+ # # Execute initial config primitive(s) #
+ # #######################################
+ primitives = {}
+
+ # Build a sequential list of the primitives to execute
+ for primitive in params['initial-config-primitive']:
+ try:
+ if primitive['name'] == 'config':
+ # This is applied when the Application is deployed
+ pass
+ else:
+ seq = primitive['seq']
+
+ primitives[seq] = {
+ 'name': primitive['name'],
+ 'parameters': self._map_primitive_parameters(
+ primitive['parameter'],
+ {'<rw_mgmt_ip>': rw_mgmt_ip}
+ ),
+ }
+
+ for primitive in sorted(primitives):
+ await self.ExecutePrimitive(
+ model_name,
+ application_name,
+ primitives[primitive]['name'],
+ callback,
+ callback_args,
+ **primitives[primitive]['parameters'],
+ )
+ except N2VCPrimitiveExecutionFailed as e:
+ self.debug.log(
+ "[N2VC] Exception executing primitive: {}".format(e)
+ )
+ raise
+
async def ExecutePrimitive(self, model_name, application_name, primitive, callback, *callback_args, **params):
try:
if not self.authenticated:
if unit:
self.log.debug("Executing primitive {}".format(primitive))
action = await unit.run_action(primitive, **params)
- action = await action.wait()
+ # action = await action.wait()
await model.disconnect()
except Exception as e:
self.log.debug("Caught exception while executing primitive: {}".format(e))
model = await self.get_model(model_name)
app = await self.get_application(model, application_name)
if app:
- self.notify_callback(model_name, application_name, "removing", callback, *callback_args)
+ # Remove this application from event monitoring
+ self.monitors[model_name].RemoveApplication(application_name)
+
+ # self.notify_callback(model_name, application_name, "removing", callback, *callback_args)
+ self.log.debug("Removing the application {}".format(application_name))
await app.remove()
+
+ # Notify the callback that this charm has been removed.
self.notify_callback(model_name, application_name, "removed", callback, *callback_args)
+
except Exception as e:
print("Caught exception: {}".format(e))
+ self.log.debug(e)
raise e
async def DestroyNetworkService(self, nsd):
return await self.set_config(application=application, config=config)
def _get_config_from_dict(self, config_primitive, values):
- """Transform the yang config primitive to dict."""
+ """Transform the yang config primitive to dict.
+
+ Expected result:
+
+ config = {
+ 'config':
+ }
+ """
config = {}
for primitive in config_primitive:
if primitive['name'] == 'config':
+ # config = self._map_primitive_parameters()
for parameter in primitive['parameter']:
param = str(parameter['name'])
if parameter['value'] == "<rw_mgmt_ip>":
return config
+ def _map_primitive_parameters(self, parameters, values):
+ params = {}
+ for parameter in parameters:
+ param = str(parameter['name'])
+ if parameter['value'] == "<rw_mgmt_ip>":
+ params[param] = str(values[parameter['value']])
+ else:
+ params[param] = str(parameter['value'])
+ return params
+
def _get_config_from_yang(self, config_primitive, values):
"""Transform the yang config primitive to dict."""
config = {}
print("connecting to model {}".format(model_name))
self.models[model_name] = await self.controller.get_model(model_name)
+ # Create an observer for this model
+ self.monitors[model_name] = VCAMonitor(model_name)
+ self.models[model_name].add_observer(self.monitors[model_name])
+
return self.models[model_name]
async def login(self):
# current_controller no longer exists
# self.log.debug("Connecting to current controller...")
# await self.controller.connect_current()
+ # await self.controller.connect(
+ # endpoint=self.endpoint,
+ # username=self.user,
+ # cacert=cacert,
+ # )
self.log.fatal("VCA credentials not configured.")
self.authenticated = True