Merge "Return the uuid of the executed primitive" v3.1
authorAdam Israel <adam.israel@canonical.com>
Mon, 7 May 2018 16:28:11 +0000 (18:28 +0200)
committerGerrit Code Review <root@osm.etsi.org>
Mon, 7 May 2018 16:28:11 +0000 (18:28 +0200)
1  2 
n2vc/vnf.py

diff --combined n2vc/vnf.py
@@@ -36,7 -36,6 +36,7 @@@ class JujuCharmNotFound(Exception)
  class JujuApplicationExists(Exception):
      """The Application already exists."""
  
 +
  class N2VCPrimitiveExecutionFailed(Exception):
      """Something failed while attempting to execute a primitive."""
  
@@@ -47,67 -46,42 +47,67 @@@ logging.getLogger('juju.client.connecti
  logging.getLogger('juju.model').setLevel(logging.WARN)
  logging.getLogger('juju.machine').setLevel(logging.WARN)
  
 +
  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":
  # Create unique models per network service
  # Document all public functions
  
 +
  class N2VC:
  
      # Juju API
          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) #
                      # This is applied when the Application is deployed
                      pass
                  else:
 -                    # TODO: We need to sort by seq, and queue the actions in order.
 -
                      seq = primitive['seq']
  
                      primitives[seq] = {
                  raise
  
      async def ExecutePrimitive(self, model_name, application_name, primitive, callback, *callback_args, **params):
+         """
+         Queue the execution of a primitive
+         returns the UUID of the executed primitive
+         """
+         uuid = None
          try:
              if not self.authenticated:
                  await self.login()
              # FIXME: This is hard-coded until model-per-ns is added
              model_name = 'default'
  
 +            model = await self.controller.get_model(model_name)
 +
              if primitive == 'config':
                  # config is special, and expecting params to be a dictionary
 -                await self.set_config(application_name, params['params'])
 +                self.log.debug("Setting charm configuration for {}".format(application_name))
 +                self.log.debug(params['params'])
 +                await self.set_config(model, application_name, params['params'])
              else:
 -                model = await self.controller.get_model(model_name)
                  app = await self.get_application(model, application_name)
                  if app:
                      # Run against the first (and probably only) unit in the app
                      if unit:
                          self.log.debug("Executing primitive {}".format(primitive))
                          action = await unit.run_action(primitive, **params)
-                         # action = await action.wait()
+                         uuid = action.id
                  await model.disconnect()
          except Exception as e:
              self.log.debug("Caught exception while executing primitive: {}".format(e))
              raise e
+         return uuid
  
      async def RemoveCharms(self, model_name, application_name, callback=None, *callback_args):
          try:
              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)
      async def DestroyNetworkService(self, nsd):
          raise NotImplementedError()
  
 -    async def GetMetrics(self, nsd, vnfd):
 -        """Get the metrics collected by the VCA."""
 -        raise NotImplementedError()
 +    async def GetMetrics(self, model_name, application_name):
 +        """Get the metrics collected by the VCA.
 +
 +        :param model_name The name of the model
 +        :param application_name The name of the application
 +        """
 +        metrics = {}
 +        model = await self.get_model(model_name)
 +        app = await self.get_application(model, application_name)
 +        if app:
 +            metrics = await app.get_metrics()
 +
 +        return metrics
  
      # Non-public methods
      async def add_relation(self, a, b, via=None):
          finally:
              await m.disconnect()
  
 -    async def apply_config(self, config, application):
 -        """Apply a configuration to the application."""
 -        print("JujuApi: Applying configuration to {}.".format(
 -            application
 -        ))
 -        return await self.set_config(application=application, config=config)
 +    async def apply_config(self, config, application):
 +        """Apply a configuration to the application."""
 +        print("JujuApi: Applying configuration to {}.".format(
 +            application
 +        ))
 +        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.
              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):
  
          return result
  
 -    async def set_config(self, application, config):
 +    async def set_config(self, model_name, application, config):
          """Apply a configuration to the application."""
          if not self.authenticated:
              await self.login()
  
 -        app = await self.get_application(self.default_model, application)
 +        app = await self.get_application(model_name, application)
          if app:
              self.log.debug("JujuApi: Setting config for Application {}".format(
                  application,
                  if config[key] != newconf[key]['value']:
                      self.log.debug("JujuApi: Config not set! Key {} Value {} doesn't match {}".format(key, config[key], newconf[key]))
  
 -    async def set_parameter(self, parameter, value, application=None):
 -        """Set a config parameter for a service."""
 -        if not self.authenticated:
 -            await self.login()
 -
 -        self.log.debug("JujuApi: Setting {}={} for Application {}".format(
 -            parameter,
 -            value,
 -            application,
 -        ))
 -        return await self.apply_config(
 -            {parameter: value},
 -            application=application,
 -        )
 +    async def set_parameter(self, parameter, value, application=None):
 +        """Set a config parameter for a service."""
 +        if not self.authenticated:
 +            await self.login()
 +    #
 +        self.log.debug("JujuApi: Setting {}={} for Application {}".format(
 +            parameter,
 +            value,
 +            application,
 +        ))
 +        return await self.apply_config(
 +            {parameter: value},
 +            application=application,
 +        )
  
      async def wait_for_application(self, name, timeout=300):
          """Wait for an application to become active."""