Refactor JujuApi to use libjuju asyncronous API 34/2134/8
authorAdam Israel <adam.israel@canonical.com>
Thu, 7 Sep 2017 23:13:24 +0000 (19:13 -0400)
committerAdam Israel <adam.israel@canonical.com>
Thu, 5 Oct 2017 15:45:54 +0000 (11:45 -0400)
This adds support for the asyncronous libjuju API, which enables better feedback to the user during charm deployment, as well as some of the goals of the N2VC module.

Pushing for gerrit

Signed-off-by: Adam Israel <adam.israel@canonical.com>
Change-Id: I731afe8f16502984eb3ace53a70c9457d730c32a

common/python/rift/mano/config_agent/operdata.py
common/python/rift/mano/utils/juju_api.py [changed mode: 0644->0755]
rwcm/plugins/rwconman/rift/tasklets/rwconmantasklet/jujuconf.py

index 83912db..e89a823 100644 (file)
@@ -16,7 +16,6 @@
 
 import asyncio
 import concurrent.futures
-import gi
 import time
 import gi
 
@@ -39,6 +38,7 @@ import rift.mano.utils.juju_api as juju
 class ConfigAgentAccountNotFound(Exception):
     pass
 
+
 class JujuClient(object):
     def __init__(self, log, ip, port, user, passwd):
         self._log = log
@@ -51,29 +51,39 @@ class JujuClient(object):
                                  server=ip, port=port,
                                  user=user, secret=passwd)
 
-
     def validate_account_creds(self):
-        status = RwcalYang.YangData_Rwcal_ConnectionStatus()
+        """Validate the account credentials.
+
+        Verifies if the account credentials can connect and login to a Juju
+        controller at the provided IP address.
+        """
+        status = "unknown"
+        details = "Connection status not known."
+
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
         try:
-            env = self._api._get_env()
-        except juju.JujuEnvError as e:
-            msg = "JujuClient: Invalid account credentials: %s", str(e)
-            self._log.error(msg)
-            raise Exception(msg)
-        except ConnectionRefusedError as e:
-            msg = "JujuClient: Wrong IP or Port: %s", str(e)
-            self._log.error(msg)
-            raise Exception(msg)
+            loop.run_until_complete(asyncio.gather(
+                self._api.logout(),
+                self._api.login(),
+                loop=loop,
+            ))
         except Exception as e:
             msg = "JujuClient: Connection Failed: %s", str(e)
             self._log.error(msg)
             raise Exception(msg)
         else:
-            status.status = "success"
-            status.details = "Connection was successful"
+            self._log.error("Success reached.")
+            status = "success"
+            details = "Connection was successful"
             self._log.info("JujuClient: Connection Successful")
+        finally:
+            loop.close()
 
-        return status
+        return RwConfigAgentYang.YangData_RwProject_Project_ConfigAgent_Account_ConnectionStatus(
+            status="unknown",
+            details="Connection status lookup not started"
+        )
 
 
 class ConfigAgentAccount(object):
@@ -136,7 +146,7 @@ class ConfigAgentAccount(object):
             try:
                 status = yield from loop.run_in_executor(
                     None,
-                    self._cfg_agent_client_plugin.validate_account_creds
+                    self._cfg_agent_client_plugin.validate_account_creds,
                     )
                 self._status = RwConfigAgentYang.YangData_RwProject_Project_ConfigAgent_Account_ConnectionStatus.from_dict(status.as_dict())
             except Exception as e:
old mode 100644 (file)
new mode 100755 (executable)
index b452b8a..40ff14c
 
 import argparse
 import asyncio
-from functools import partial
 import logging
 import os
 import ssl
-import sys
-import time
 
-try:
-    from jujuclient.juju1.environment import Environment as Env1
-    from jujuclient.juju2.environment import Environment as Env2
-except ImportError as e:
-    # Try importing older jujuclient
-    from jujuclient import Environment as Env1
+import juju.loop
+from juju.controller import Controller
+from juju.model import Model, ModelObserver
 
 try:
     ssl._create_default_https_context = ssl._create_unverified_context
@@ -102,27 +96,62 @@ class JujuActionExecError(JujuActionError):
     pass
 
 
+class JujuAuthenticationError(Exception):
+    pass
+
+
+class JujuMonitor(ModelObserver):
+    """Monitor state changes within the Juju Model."""
+    # async def on_change(self, delta, old, new, model):
+    #     """React to changes in the Juju model."""
+    #
+    #     # TODO: Setup the hook to update the UI if the status of a unit changes
+    #     # to be used when deploying a charm and waiting for it to be "ready"
+    #     if delta.entity in ['application', 'unit'] and delta.type == "change":
+    #         pass
+    #
+    #     # TODO: Add a hook when an action is complete
+
+    pass
+
+
 class JujuApi(object):
-    '''
-    JujuApi wrapper on jujuclient library
+    """JujuApi wrapper on jujuclient library.
 
     There should be one instance of JujuApi for each VNF manged by Juju.
 
     Assumption:
         Currently we use one unit per service/VNF. So once a service
         is deployed, we store the unit name and reuse it
-'''
+    """
+
     log = None
+    controller = None
+    models = {}
+    model = None
+    model_name = None
+    model_uuid = None
+    authenticated = False
+
+    def __init__(self,
+                 log=None,
+                 loop=None,
+                 server='127.0.0.1',
+                 port=17070,
+                 user='admin',
+                 secret=None,
+                 version=None,
+                 model_name='default',
+                 ):
+        """Initialize with the Juju credentials."""
+
+        if log:
+            self.log = log
+        else:
+            self.log = logging.getLogger(__name__)
+
+        self.log.debug('JujuApi instantiated')
 
-    def __init__ (self,
-                  log=None,
-                  loop=None,
-                  server='127.0.0.1',
-                  port=17070,
-                  user='admin',
-                  secret=None,
-                  version=None):
-        '''Initialize with the Juju credentials'''
         self.server = server
         self.port = port
 
@@ -132,939 +161,502 @@ class JujuApi(object):
         else:
             self.user = 'user-{}'.format(user)
 
-        self.loop = loop
+        self.endpoint = '%s:%d' % (server, int(port))
+
+        self.model_name = model_name
+
+        if loop:
+            self.loop = loop
+
+    def __del__(self):
+        """Close any open connections."""
+        yield self.logout()
+
+    async def apply_config(self, config, application):
+        """Apply a configuration to the application."""
+        return await self.set_config(application=application, config=config)
+
+    async def deploy_application(self, charm, name="", path=""):
+        """Deploy an application."""
+        if not self.authenticated:
+            await self.login()
+
+        app = await self.get_application(name)
+        if app is None:
+            # TODO: Handle the error if the charm isn't found.
+            app = await self.model.deploy(
+                path,
+                application_name=name,
+                series='xenial',
+            )
+        return app
+    deploy_service = deploy_application
+
+    async def get_action_status(self, uuid):
+        """Get the status of an action."""
+        if not self.authenticated:
+            await self.login()
+
+        action = await self.model.wait_for_action(uuid)
+        return action.status
+
+    async def get_application(self, application):
+        """Get the deployed application."""
+        if not self.authenticated:
+            await self.login()
+
+        app = None
+        if application in self.model.applications:
+            app = self.model.applications[application]
+        return app
+
+    async def get_application_status(self, application, status=None):
+        """Get the status of an application."""
+        if not self.authenticated:
+            await self.login()
+
+        status = None
+        app = await self.get_application(application)
+        if app:
+            status = app.status
 
-        if log is not None:
-            self.log = log
-        else:
-            self.log = JujuApi._get_logger()
+        return status
+    get_service_status = get_application_status
 
-        if self.log is None:
-            raise JujuApiError("Logger not defined")
+    async def get_config(self, application):
+        """Get the configuration of an application."""
+        if not self.authenticated:
+            await self.login()
 
-        self.version = None
-        if version:
-            self.version = version
-        else:
-            try:
-                if Env2:
-                    pass
-            except NameError:
-                self.log.warn("Using older version of Juju client, which " \
-                              "supports only Juju 1.x")
-                self.version = 1
-
-        endpoint = 'wss://%s:%d' % (server, int(port))
-        self.endpoint = endpoint
-
-        self.charm = None  # Charm used
-        self.service = None  # Service deployed
-        self.units = []  # Storing as list to support more units in future
-
-        self.destroy_retries = 25 # Number retires to destroy service
-        self.retry_delay = 5 # seconds
-
-    def __str__(self):
-        return ("JujuApi-{}".format(self.endpoint))
-
-    @classmethod
-    def _get_logger(cls):
-        if cls.log is not None:
-            return cls.log
-
-        fmt = logging.Formatter(
-            '%(asctime)-23s %(levelname)-5s  (%(name)s@%(process)d:' \
-            '%(filename)s:%(lineno)d) - %(message)s')
-        stderr_handler = logging.StreamHandler(stream=sys.stderr)
-        stderr_handler.setFormatter(fmt)
-        logging.basicConfig(level=logging.DEBUG)
-        cls.log = logging.getLogger('juju-api')
-        cls.log.addHandler(stderr_handler)
-
-        return cls.log
-
-    @staticmethod
-    def format_charm_name(name):
-        '''Format the name to valid charm name
-
-        Charm service name accepts only a to z and -.
-        '''
-
-        new_name = ''
-        for c in name:
-            if c.isdigit():
-                c = chr(97 + int(c))
-            elif not c.isalpha():
-                c = "-"
-            new_name += c
-        return new_name.lower()
-
-    def _get_version_tag(self, tag):
-        version_tag_map = {
-            'applications': {
-                1: 'Services',
-                2: 'applications',
-            },
-            'units': {
-                1: 'Units',
-                2: 'units',
-            },
-            'status': {
-                1: 'Status',
-                2: 'status',
-            },
-            'workload-status': {
-                1: 'Workload',
-                2: 'workload-status',
-            },
-            'charm-url': {
-                1: 'CharmURL',
-                2: 'charm-url',
-            },
-        }
+        config = None
+        app = await self.get_application(application)
+        if app:
+            config = await app.get_config()
 
-        return version_tag_map[tag][self.version]
-
-    def _get_env1(self):
-        try:
-            env = Env1(self.endpoint)
-            l = env.login(self.secret, user=self.user)
-            return env
-
-        except ConnectionRefusedError as e:
-            msg = "{}: Failed Juju 1.x connect: {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise e
-
-        except Exception as e:
-            msg = "{}: Failed Juju 1.x connect: {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuEnvError(msg)
-
-    def _get_env2(self):
-        try:
-            env = Env2(self.endpoint)
-            l = env.login(self.secret, user=self.user)
-        except KeyError as e:
-            msg = "{}: Failed Juju 2.x connect: {}".format(self, e)
-            self.log.debug(msg)
-            raise JujuVersionError(msg)
-
-        try:
-            models = env.models.list()
-            for m in models['user-models']:
-                if m['model']['name'] == 'default':
-                    mep =  '{}/model/{}/api'.format(self.endpoint,
-                                                    m['model']['uuid'])
-                    model = Env2(mep, env_uuid=m['model']['uuid'])
-                    l = model.login(self.secret, user=self.user)
-                    break
-
-            if model is None:
-                raise
-
-            return model
-
-        except Exception as e:
-            msg = "{}: Failed logging to model: {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            env.close()
-            raise JujuModelError(msg)
-
-    def _get_env(self):
-        self.log.debug("{}: Connect to endpoint {}".
-                      format(self, self.endpoint))
-
-        if self.version is None:
-            # Try version 2 first
-            try:
-                env = self._get_env2()
-                self.version = 2
-
-            except JujuVersionError as e:
-                self.log.info("Unable to login as Juju 2.x, trying 1.x")
-                env = self._get_env1()
-                self.version = 1
-
-            return env
-
-        elif self.version == 2:
-            return self._get_env2()
-
-        elif self.version == 1:
-            return self._get_env1()
+        return config
 
-        else:
-            msg = "{}: Unknown version set: {}".format(self, self.version)
-            self.log.error(msg)
-            raise JujuVersionError(msg)
-
-    @asyncio.coroutine
-    def get_env(self):
-        ''' Connect to the Juju controller'''
-        env = yield from self.loop.run_in_executor(
-            None,
-            self._get_env,
-        )
-        return env
-
-    def _get_status(self, env=None):
-        if env is None:
-            env = self._get_env()
-
-        try:
-            status = env.status()
-            return status
-
-        except Exception as e:
-            msg = "{}: exception in getting status: {}". \
-                  format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuStatusError(msg)
-
-    @asyncio.coroutine
-    def get_status(self, env=None):
-        '''Get Juju controller status'''
-        pf = partial(self._get_status, env=env)
-        status = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return status
+    async def get_model(self, name='default'):
+        """Get a model from the Juju Controller.
 
-    def get_all_units(self, status, service=None):
-        '''Parse the status and get the units'''
-        results = {}
-        services = status.get(self._get_version_tag('applications'), {})
-
-        for svc_name, svc_data in services.items():
-            if service and service != svc_name:
-                continue
-            units = svc_data[self._get_version_tag('units')] or {}
-
-            results[svc_name] = {}
-            for unit in units:
-                results[svc_name][unit] = \
-                        units[unit][self._get_version_tag('workload-status')] \
-                        [self._get_version_tag('status')] or None
-        return results
-
-
-    def _get_service_units(self, service=None, status=None, env=None):
-        if service is None:
-            service = self.service
-
-        # Optimizing calls to Juju, as currently we deploy only 1 unit per
-        # service.
-        # if self.service == service and len(self.units):
-        #     return self.units
-
-        if env is None:
-            env = self._get_env()
-
-        if status is None:
-            status = self._get_status(env=env)
-
-        try:
-            resp = self.get_all_units(status, service=service)
-            self.log.debug("Get all units: {}".format(resp))
-            units = set(resp[service].keys())
-
-            if self.service == service:
-                self.units = units
-
-            return units
-
-        except Exception as e:
-            msg = "{}: exception in get units {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuUnitsError(msg)
-
-    @asyncio.coroutine
-    def get_service_units(self, service=None, status=None, env=None):
-        '''Get the unit names for a service'''
-        pf = partial(self._get_service_units,
-                     service=service,
-                     status=status,
-                     env=env)
-        units = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return units
-
-    def _get_service_status(self, service=None, status=None, env=None):
-        if env is None:
-            env = self._get_env()
-
-        if status is None:
-            status = self._get_status(env=env)
-
-        if service is None:
-            service = self.service
-
-        try:
-            srv_status = status[self._get_version_tag('applications')] \
-                         [service][self._get_version_tag('status')] \
-                         [self._get_version_tag('status')]
-            self.log.debug("{}: Service {} status is {}".
-                           format(self, service, srv_status))
-            return srv_status
-
-        except KeyError as e:
-            self.log.info("self: Did not find service {}, e={}".format(self, service, e))
-            return 'NA'
-
-        except Exception as e:
-            msg = "{}: exception checking service status for {}, e {}". \
-                  format(self, service, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuStatusError(msg)
-
-
-    @asyncio.coroutine
-    def get_service_status(self, service=None, status=None, env=None):
-        ''' Get service status
-
-            maintenance : The unit is not yet providing services, but is actively doing stuff.
-            unknown : Service has finished an event but the charm has not called status-set yet.
-            waiting : Service is unable to progress to an active state because of dependency.
-            blocked : Service needs manual intervention to get back to the Running state.
-            active  : Service correctly offering all the services.
-            NA      : Service is not deployed
-        '''
-        pf = partial(self._get_service_status,
-                     service=service,
-                     status=status,
-                     env=env)
-        srv_status = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return srv_status
-
-    def _is_service_deployed(self, service=None, status=None, env=None):
-        resp = self._get_service_status(service=service,
-                                        status=status,
-                                        env=env)
-
-        if resp not in ['terminated', 'NA']:
-            return True
-
-        return False
-
-    @asyncio.coroutine
-    def is_service_deployed(self, service=None, status=None, env=None):
-        '''Check if the service is deployed'''
-        pf = partial(self._is_service_deployed,
-                     service=service,
-                     status=status,
-                     env=env)
-        rc = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return rc
-
-    def _is_service_error(self, service=None, status=None, env=None):
-        resp = self._get_service_status(service=service,
-                                        status=status,
-                                        env=env)
-
-        if resp in ['error']:
-            return True
-
-        return False
-
-    @asyncio.coroutine
-    def is_service_error(self, service=None, status=None, env=None):
-        '''Check if the service is in error state'''
-        pf = partial(self._is_service_error,
-                     service=service,
-                     status=status,
-                     env=env)
-        rc = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return rc
-
-    def _is_service_maint(self, service=None, status=None, env=None):
-        resp = self._get_service_status(service=service,
-                                        status=status,
-                                        env=env)
-
-        if resp in ['maintenance']:
-            return True
-
-        return False
-
-    @asyncio.coroutine
-    def is_service_maint(self, service=None, status=None, env=None):
-        '''Check if the service is in error state'''
-        pf = partial(self._is_service_maint,
-                     service=service,
-                     status=status,
-                     env=env)
-        rc = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return rc
-
-    def _is_service_active(self, service=None, status=None, env=None):
-        resp = self._get_service_status(service=service,
-                                        status=status,
-                                        env=env)
-
-        if resp in ['active']:
-            return True
-
-        return False
-
-    @asyncio.coroutine
-    def is_service_active(self, service=None, status=None, env=None):
-        '''Check if the service is active'''
-        pf = partial(self._is_service_active,
-                     service=service,
-                     status=status,
-                     env=env)
-        rc = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return rc
-
-    def _is_service_blocked(self, service=None, status=None, env=None):
-        resp = self._get_service_status(service=service,
-                                        status=status,
-                                        env=env)
-
-        if resp in ['blocked']:
-            return True
-
-        return False
-
-    @asyncio.coroutine
-    def is_service_blocked(self, service=None, status=None, env=None):
-        '''Check if the service is blocked'''
-        pf = partial(self._is_service_blocked,
-                     service=service,
-                     status=status,
-                     env=env)
-        rc = yield from self.loop.run_in_executor(
+        Note: Model objects returned must call disconnected() before it goes
+        out of scope."""
+        if not self.authenticated:
+            await self.login()
+
+        model = Model()
+
+        uuid = await self.get_model_uuid(name)
+
+        await model.connect(
+            self.endpoint,
+            uuid,
+            self.user,
+            self.secret,
             None,
-            pf,
         )
-        return rc
 
-    def _is_service_up(self, service=None, status=None, env=None):
-        resp = self._get_service_status(service=service,
-                                        status=status,
-                                        env=env)
+        return model
 
-        if resp in ['active', 'blocked']:
-            return True
+    async def get_model_uuid(self, name='default'):
+        """Get the UUID of a model.
 
-        return False
+        Iterate through all models in a controller and find the matching model.
+        """
+        if not self.authenticated:
+            await self.login()
 
-    @asyncio.coroutine
-    def is_service_up(self, service=None, status=None, env=None):
-        '''Check if the service is installed and up'''
-        pf = partial(self._is_service_up,
-                     service=service,
-                     status=status,
-                     env=env)
+        uuid = None
 
-        rc = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return rc
+        models = await self.controller.get_models()
+        for model in models.user_models:
+            if model.model.name == name:
+                uuid = model.model.uuid
+                break
 
-    def _apply_config(self, config, service=None, env=None):
-        if service is None:
-            service = self.service
+        return uuid
 
-        if config is None or len(config) == 0:
-            self.log.warn("{}: Empty config passed for service {}".
-                          format(self, service))
-            return
+    async def get_status(self):
+        """Get the model status."""
+        if not self.authenticated:
+            await self.login()
 
-        if env is None:
-            env = self._get_env()
-
-        status = self._get_status(env=env)
-
-        if not self._is_service_deployed(service=service,
-                                         status=status,
-                                         env=env):
-            raise JujuSrvNotDeployedError("{}: service {} is not deployed".
-                                          format(self, service))
-
-        self.log.debug("{}: Config for service {} update to: {}".
-                       format(self, service, config))
-        try:
-            # Try to fix error on service, most probably due to config issue
-            if self._is_service_error(service=service, status=status, env=env):
-                self._resolve_error(service=service, env=env)
-
-            if self.version == 2:
-                env.service.set(service, config)
-            else:
-                env.set_config(service, config)
-
-        except Exception as e:
-            self.log.error("{}: exception setting config for {} with {}, e {}".
-                           format(self, service, config, e))
-            self.log.exception(e)
-            raise e
-
-    @asyncio.coroutine
-    def apply_config(self, config, service=None, env=None, wait=True):
-        '''Apply a config on the service'''
-        pf = partial(self._apply_config,
-                     config,
-                     service=service,
-                     env=env)
-        yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
+        if not self.model:
+            self.model = self.get_model(self.model_name)
 
-        if wait:
-            # Wait till config finished applying
-            self.log.debug("{}: Wait for config apply to finish".
-                           format(self))
-            delay = 3  # secs
-            maint = True
-            while maint:
-                # Sleep first to give time for config_changed hook to be invoked
-                yield from asyncio.sleep(delay, loop=self.loop)
-                maint = yield from self.is_service_maint(service=service,
-                                                         env=env)
-
-        err = yield from self.is_service_error(service=service, env=env)
-        if err:
-            self.log.error("{}: Service is in error state".
-                           format(self))
-            return False
-
-        self.log.debug("{}: Finished applying config".format(self))
-        return True
-
-    def _set_parameter(self, parameter, value, service=None):
-        return self._apply_config({parameter : value}, service=service)
-
-    @asyncio.coroutine
-    def set_parameter(self, parameter, value, service=None):
-        '''Set a config parameter for a service'''
-        return self.apply_config({parameter : value}, service=service)
-
-    def _resolve_error(self, service=None, status=None, env=None):
-        if env is None:
-            env = self._get_env()
-
-        if status is None:
-            status = self._get_status(env=env)
-
-        if service is None:
-            service = self.service
-
-        if env is None:
-            env = self._get_env()
-        if self._is_service_deployed(service=service, status=status):
-            units = self.get_all_units(status, service=service)
-
-            for unit, ustatus in units[service].items():
-                if ustatus == 'error':
-                    self.log.info("{}: Found unit {} with status {}".
-                                  format(self, unit, ustatus))
-                    try:
-                        # Takes the unit name as service_name/idx unlike action
-                        env.resolved(unit)
-
-                    except Exception as e:
-                        msg = "{}: Resolve on unit {}: {}". \
-                              format(self, unit, e)
-                        self.log.warn(msg)
-
-    @asyncio.coroutine
-    def resolve_error(self, service=None, status=None, env=None):
-        '''Resolve units in error state'''
-        pf = partial(self._resolve_error,
-                     service=service,
-                     status=status,
-                     env=env)
-        yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
+        class model_state:
+            applications = {}
+            machines = {}
+            relations = {}
+
+        status = model_state()
+        status.applications = self.model.applications
+        status.machines = self.model.machines
 
-    def _deploy_service(self, charm, service,
-                        path=None, config=None, env=None):
-        self.log.debug("{}: Deploy service for charm {}({}) with service {}".
-                       format(self, charm, path, service))
+        return status
 
-        if env is None:
-            env = self._get_env()
+    async def is_application_active(self, application):
+        """Check if the application is in an active state."""
+        if not self.authenticated:
+            await self.login()
+
+        state = False
+        status = await self.get_application_status(application)
+        if status and status in ['active']:
+            state = True
+        return state
+    is_service_active = is_application_active
+
+    async def is_application_blocked(self, application):
+        """Check if the application is in a blocked state."""
+        if not self.authenticated:
+            await self.login()
+
+        state = False
+        status = await self.get_application_status(application)
+        if status and status in ['blocked']:
+            state = True
+        return state
+    is_service_blocked = is_application_blocked
+
+    async def is_application_deployed(self, application):
+        """Check if the application is in a deployed state."""
+        if not self.authenticated:
+            await self.login()
+
+        state = False
+        status = await self.get_application_status(application)
+        if status and status in ['active']:
+            state = True
+        return state
+    is_service_deployed = is_application_deployed
+
+    async def is_application_error(self, application):
+        """Check if the application is in an error state."""
+        if not self.authenticated:
+            await self.login()
+
+        state = False
+        status = await self.get_application_status(application)
+        if status and status in ['error']:
+            state = True
+        return state
+    is_service_error = is_application_error
+
+    async def is_application_maint(self, application):
+        """Check if the application is in a maintenance state."""
+        if not self.authenticated:
+            await self.login()
+
+        state = False
+        status = await self.get_application_status(application)
+        if status and status in ['maintenance']:
+            state = True
+        return state
+    is_service_maint = is_application_maint
+
+    async def is_application_up(self, application=None):
+        """Check if the application is up."""
+        if not self.authenticated:
+            await self.login()
+        state = False
+
+        status = await self.get_application_status(application)
+        if status and status in ['active', 'blocked']:
+            state = True
+        return state
+    is_service_up = is_application_up
+
+    async def login(self):
+        """Login to the Juju controller."""
+        if self.authenticated:
+            return
+        cacert = None
+        self.controller = Controller()
+
+        if self.secret:
+            await self.controller.connect(
+                self.endpoint,
+                self.user,
+                self.secret,
+                cacert,
+            )
+        else:
+            await self.controller.connect_current()
 
-        self.service = service
-        self.charm = charm
+        self.authenticated = True
+        self.model = await self.get_model(self.model_name)
 
-        if self._is_service_deployed(service=service, env=env):
-            self.log.info("{}: Charm service {} already deployed".
-                          format (self, service))
-            if config:
-                self._apply_config(config, service=service, env=env)
+    async def logout(self):
+        """Logout of the Juju controller."""
+        if not self.authenticated:
             return
 
-        series = "trusty"
-
-        deploy_to = None
-        if self.version == 1:
-            deploy_to = "lxc:0"
-
-        if path is None:
-            prefix=os.getenv('RIFT_INSTALL', '/')
-            path = os.path.join(prefix, 'usr/rift/charms', series, charm)
-
-        try:
-            self.log.debug("{}: Local charm settings: dir={}, series={}".
-                           format(self, path, series))
-            result = env.add_local_charm_dir(path, series)
-            url = result[self._get_version_tag('charm-url')]
-
-        except Exception as e:
-            msg = '{}: Error setting local charm directory {} for {}: {}'. \
-                  format(self, path, service, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuAddCharmError(msg)
-
-        try:
-            self.log.debug("{}: Deploying using: service={}, url={}, to={}, config={}".
-                           format(self, service, url, deploy_to, config))
-            env.deploy(service, url, config=config, machine_spec=deploy_to)
-
-        except Exception as e:
-            msg = '{}: Error deploying {}: {}'.format(self, service, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuDeployError(msg)
-
-    @asyncio.coroutine
-    def deploy_service(self, charm, service,
-                       wait=False, timeout=300,
-                       path=None, config=None):
-        '''Deploy a service using the charm name provided'''
-        env = yield from self.get_env()
-
-        pf = partial(self._deploy_service,
-                     charm,
-                     service,
-                     path=path,
-                     config=config,
-                     env=env)
-        yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
+        if self.model:
+            await self.model.disconnect()
+            self.model = None
+        if self.controller:
+            await self.controller.disconnect()
+            self.controller = None
+
+        self.authenticated = False
+
+    async def remove_application(self, name):
+        """Remove the application."""
+        if not self.authenticated:
+            await self.login()
+
+        app = await self.get_application(name)
+        if app:
+            await app.destroy()
+
+    async def resolve_error(self, application=None, status=None):
+        """Resolve units in error state."""
+        if not self.authenticated:
+            await self.login()
+
+        app = await self.get_application(application)
+        if app:
+            for unit in app.units:
+                app.resolved(retry=True)
+
+    async def run_action(self, application, action_name, **params):
+        """Execute an action and return an Action object."""
+        if not self.authenticated:
+            await self.login()
+        result = {
+            'status': '',
+            'action': {
+                'tag': None,
+                'results': None,
+            }
+        }
+        app = await self.get_application(application)
+        if app:
+            # We currently only have one unit per application
+            # so use the first unit available.
+            unit = app.units[0]
 
-        rc = True
-        if wait is True:
-            # Wait for the deployed units to start
-            try:
-                self.log.debug("{}: Waiting for service {} to come up".
-                               format(self, service))
-                rc = yield from self.wait_for_service(timeout=timeout, env=env)
-
-            except Exception as e:
-                msg = '{}: Error starting all units for {}: {}'. \
-                      format(self, service, e)
-                self.log.error(msg)
-                self.log.exception(e)
-                raise JujuWaitUnitsError(msg)
-
-        return rc
-
-    @asyncio.coroutine
-    def wait_for_service(self, service=None, timeout=0, env=None):
-        '''Wait for the service to come up'''
-        if service is None:
-            service = self.service
-
-        if env is None:
-            env = yield from self.get_env()
-
-        status = yield from self.get_status(env=env)
-
-        if self._is_service_up(service=service, status=status, env=env):
-            self.log.debug("{}: Service {} is already up".
-                               format(self, service))
-            return True
-
-        # Check if service is deployed
-        if not self._is_service_deployed(service=service, status=status, env=env):
-            raise JujuSrvNotDeployedError("{}: service {} is not deployed".
-                                          format(self, service))
-
-        if timeout < 0:
-            timeout = 0
-
-        count = 0
-        delay = self.retry_delay # seconds
-        self.log.debug("{}: In wait for service {}".format(self, service))
-
-        start_time = time.time()
-        max_time = time.time() + timeout
-        while timeout != 0 and (time.time() <= max_time):
-            count += 1
-            rc = yield from self.is_service_up(service=service, env=env)
-            if rc:
-                self.log.debug("{}: Service {} is up after {} seconds".
-                               format(self, service, time.time()-start_time))
-                return True
-            yield from asyncio.sleep(delay, loop=self.loop)
-        return False
-
-    def _destroy_service(self, service=None):
-        '''Destroy a service on Juju controller'''
-        self.log.debug("{}: Destroy charm service: {}".format(self,service))
-
-        if service is None:
-            service = self.service
-
-        env = self._get_env()
-
-        status = self._get_status(env=env)
-
-        count = 0
-        while self._is_service_deployed(service=service, status=status, env=env):
-            count += 1
-            self.log.debug("{}: Destroy service {}, count {}".
-                           format(self, service, count))
-
-            if count > self.destroy_retries:
-                msg = "{}: Not able to destroy service {} after {} tries". \
-                      format(self, service, count)
-                self.log.error(msg)
-                raise JujuDestroyError(msg)
-
-
-            if self._is_service_error(service=service, status=status):
-                self._resolve_error(service, status)
-
-            try:
-                env.destroy_service(service)
-
-            except Exception as e:
-                msg = "{}: Exception when running destroy on service {}: {}". \
-                      format(self, service, e)
-                self.log.error(msg)
-                self.log.exception(e)
-                raise JujuDestroyError(msg)
-
-            time.sleep(self.retry_delay)
-            status = self._get_status(env=env)
-
-        self.log.debug("{}: Destroyed service {} ({})".
-                       format(self, service, count))
-
-    @asyncio.coroutine
-    def destroy_service(self, service=None):
-        '''Destroy a service on Juju controller'''
-        pf = partial(self._destroy_service,
-                     service=service)
-        yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
+            action = await unit.run_action(action_name, **params)
 
+            # Wait for the action to complete
+            await action.wait()
 
-    def _get_action_status(self, action_tag, env=None):
-        if env is None:
-            env = self._get_env()
-
-        if not action_tag.startswith('action-'):
-            action_tag = 'action-{}'.format(action_tag)
-
-        try:
-            action = env.actions
-        except Exception as e:
-            msg = "{}: exception in Action API: {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuActionApiError(msg)
-
-        try:
-            status = action.info([{'Tag': action_tag}])
-
-            self.log.debug("{}: Action {} status {}".
-                           format(self, action_tag, status))
-            return status['results'][0]
-
-        except Exception as e:
-            msg = "{}: exception in get action status {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuActionInfoError(msg)
-
-    @asyncio.coroutine
-    def get_action_status(self, action_tag, env=None):
-        '''
-        Get the status of an action queued on the controller
-
-        responds with the action status, which is one of three values:
-
-         - completed
-         - pending
-         - failed
-
-         @param action_tag - the action UUID return from the enqueue method
-         eg: action-3428e20d-fcd7-4911-803b-9b857a2e5ec9
-        '''
-        pf = partial(self._get_action_status,
-                     action_tag,
-                     env=env,)
-        status = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
-        return status
+            result['status'] = action.status
+            result['action']['tag'] = action.data['id']
+            result['action']['results'] = action.results
 
-    def _execute_action(self, action_name, params, service=None, env=None):
-        '''Execute the action on all units of a service'''
-        if service is None:
-            service = self.service
-
-        if env is None:
-            env = self._get_env()
-
-        try:
-            action = env.actions
-        except Exception as e:
-            msg = "{}: exception in Action API: {}".format(self, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuActionApiError(msg)
-
-        units = self._get_service_units(service)
-        self.log.debug("{}: Apply action {} on units {}".
-                       format(self, action_name, units))
-
-        # Rename units from <service>/<n> to unit-<service>-<n>
-        unit_tags = []
-        for unit in units:
-            idx = int(unit[unit.index('/')+1:])
-            unit_name = "unit-%s-%d" % (service, idx)
-            unit_tags.append(unit_name)
-        self.log.debug("{}: Unit tags for action: {}".
-                       format(self, unit_tags))
-
-        try:
-            result = action.enqueue_units(unit_tags, action_name, params)
-            self.log.debug("{}: Response for action: {}".
-                           format(self, result))
-            return result['results'][0]
-
-        except Exception as e:
-            msg = "{}: Exception enqueing action {} on units {} with " \
-                  "params {}: {}".format(self, action, unit_tags, params, e)
-            self.log.error(msg)
-            self.log.exception(e)
-            raise JujuActionExecError(msg)
-
-    @asyncio.coroutine
-    def execute_action(self, action_name, params, service=None, env=None):
-        '''Execute an action for a service on the controller
-
-        Currently, we execute the action on all units of the service
-        '''
-        pf = partial(self._execute_action,
-                     action_name,
-                     params,
-                     service=service,
-                     env=env)
-        result = yield from self.loop.run_in_executor(
-            None,
-            pf,
-        )
         return result
+    execute_action = run_action
 
+    async def set_config(self, application, config):
+        """Apply a configuration to the application."""
+        if not self.authenticated:
+            await self.login()
 
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(description='Test Juju')
-    parser.add_argument("-s", "--server", default='10.0.202.49', help="Juju controller")
-    parser.add_argument("-u", "--user", default='admin', help="User, default user-admin")
-    parser.add_argument("-p", "--password", default='nfvjuju', help="Password for the user")
-    parser.add_argument("-P", "--port", default=17070, help="Port number, default 17070")
-    parser.add_argument("-d", "--directory", help="Local directory for the charm")
-    parser.add_argument("--service", help="Charm service name")
-    parser.add_argument("--vnf-ip", help="IP of the VNF to configure")
-    args = parser.parse_args()
+        app = await self.get_application(application)
+        if app:
+            await app.set_config(config)
 
-    api = JujuApi(server=args.server,
-                  port=args.port,
-                  user=args.user,
-                  secret=args.password)
+    async def set_parameter(self, parameter, value, application=None):
+        """Set a config parameter for a service."""
+        if not self.authenticated:
+            await self.login()
 
-    env = api._get_env()
-    if env is None:
-        raise "Not able to login to the Juju controller"
+        return await self.apply_config(
+            {parameter: value},
+            application=application,
+        )
 
-    print("Status: {}".format(api._get_status(env=env)))
+    async def wait_for_application(self, name, timeout=300):
+        """Wait for an application to become active."""
+        if not self.authenticated:
+            await self.login()
 
-    if args.directory and args.service:
-        # Deploy the charm
-        charm = os.path.basename(args.directory)
-        api._deploy_service(charm, args.service,
-                            path=args.directory,
-                            env=env)
+        app = await self.get_application(name)
+        if app:
+            await self.model.block_until(
+                lambda: all(
+                    unit.agent_status == 'idle'
+                    and unit.workload_status
+                    in ['active', 'unknown'] for unit in app.units
+                    ),
+                timeout=timeout,
+                )
 
-        while not api._is_service_up():
-            time.sleep(5)
 
-        print ("Service {} is deployed with status {}".
-               format(args.service, api._get_service_status()))
+def get_argparser():
+    parser = argparse.ArgumentParser(description='Test Juju')
+    parser.add_argument(
+        "-s", "--server",
+        default='10.0.202.49',
+        help="Juju controller"
+    )
+    parser.add_argument(
+        "-u", "--user",
+        default='admin',
+        help="User, default user-admin"
+    )
+    parser.add_argument(
+        "-p", "--password",
+        default='',
+        help="Password for the user"
+    )
+    parser.add_argument(
+        "-P", "--port",
+        default=17070,
+        help="Port number, default 17070"
+    )
+    parser.add_argument(
+        "-d", "--directory",
+        help="Local directory for the charm"
+    )
+    parser.add_argument(
+        "--application",
+        help="Charm name"
+    )
+    parser.add_argument(
+        "--vnf-ip",
+        help="IP of the VNF to configure"
+    )
+    parser.add_argument(
+        "-m", "--model",
+        default='default',
+        help="The model to connect to."
+    )
+    return parser.parse_args()
 
-        if args.vnf_ip and \
-           ('clearwater-aio' in args.directory):
-            # Execute config on charm
-            api._apply_config({'proxied_ip': args.vnf_ip})
 
-            while not api._is_service_active():
-                time.sleep(10)
+if __name__ == "__main__":
+    args = get_argparser()
 
-            print ("Service {} is in status {}".
-                   format(args.service, api._get_service_status()))
+    # Set logging level to debug so we can see verbose output from the
+    # juju library.
+    logging.basicConfig(level=logging.DEBUG)
 
-            res = api._execute_action('create-update-user', {'number': '125252352525',
-                                                             'password': 'asfsaf'})
+    # Quiet logging from the websocket library. If you want to see
+    # everything sent over the wire, set this to DEBUG.
+    ws_logger = logging.getLogger('websockets.protocol')
+    ws_logger.setLevel(logging.INFO)
 
-            print ("Action 'creat-update-user response: {}".format(res))
+    endpoint = '%s:%d' % (args.server, int(args.port))
 
-            status = res['status']
-            while status not in [ 'completed', 'failed' ]:
-                time.sleep(2)
-                status = api._get_action_status(res['action']['tag'])['status']
+    loop = asyncio.get_event_loop()
 
-                print("Action status: {}".format(status))
+    api = JujuApi(server=args.server,
+                  port=args.port,
+                  user=args.user,
+                  secret=args.password,
+                  loop=loop,
+                  log=ws_logger,
+                  model_name=args.model
+                  )
 
-            # This action will fail as the number is non-numeric
-            res = api._execute_action('delete-user', {'number': '125252352525asf'})
+    juju.loop.run(api.login())
 
-            print ("Action 'delete-user response: {}".format(res))
+    status = juju.loop.run(api.get_status())
 
-            status = res['status']
-            while status not in [ 'completed', 'failed' ]:
-                time.sleep(2)
-                status = api._get_action_status(res['action']['tag'])['status']
+    print('Applications:', list(status.applications.keys()))
+    print('Machines:', list(status.machines.keys()))
 
-                print("Action status: {}".format(status))
+    if args.directory and args.application:
+        # Deploy the charm
+        charm = os.path.basename(args.directory)
+        juju.loop.run(
+            api.deploy_application(charm,
+                                   name=args.application,
+                                   path=args.directory,
+                                   )
+        )
+
+        juju.loop.run(api.wait_for_application(charm))
+
+        # Wait for the service to come up
+        up = juju.loop.run(api.is_application_up(charm))
+        print("Application is {}".format("up" if up else "down"))
+
+        print("Service {} is deployed".format(args.application))
+
+        ###########################
+        # Execute config on charm #
+        ###########################
+        config = juju.loop.run(api.get_config(args.application))
+        hostname = config['ssh-username']['value']
+        rhostname = hostname[::-1]
+
+        # Apply the configuration
+        juju.loop.run(api.apply_config(
+            {'ssh-username': rhostname}, application=args.application
+        ))
+
+        # Get the configuration
+        config = juju.loop.run(api.get_config(args.application))
+
+        # Verify the configuration has been updated
+        assert(config['ssh-username']['value'] == rhostname)
+
+        ####################################
+        # Get the status of an application #
+        ####################################
+        status = juju.loop.run(api.get_application_status(charm))
+        print("Application Status: {}".format(status))
+
+        ###########################
+        # Execute a simple action #
+        ###########################
+        result = juju.loop.run(api.run_action(charm, 'get-ssh-public-key'))
+        print("Action {} status is {} and returned {}".format(
+            result['status'],
+            result['action']['tag'],
+            result['action']['results']
+        ))
+
+        #####################################
+        # Execute an action with parameters #
+        #####################################
+        result = juju.loop.run(
+            api.run_action(charm, 'run', command='hostname')
+        )
+        print("Action {} status is {} and returned {}".format(
+            result['status'],
+            result['action']['tag'],
+            result['action']['results']
+        ))
+
+    juju.loop.run(api.logout())
+
+    loop.close()
+
+    #     if args.vnf_ip and \
+    #        ('clearwater-aio' in args.directory):
+    #         # Execute config on charm
+    #         api._apply_config({'proxied_ip': args.vnf_ip})
+    #
+    #         while not api._is_service_active():
+    #             time.sleep(10)
+    #
+    #         print ("Service {} is in status {}".
+    #                format(args.service, api._get_service_status()))
+    #
+    #         res = api._execute_action('create-update-user', {'number': '125252352525',
+    #                                                          'password': 'asfsaf'})
+    #
+    #         print ("Action 'creat-update-user response: {}".format(res))
+    #
+    #         status = res['status']
+    #         while status not in [ 'completed', 'failed' ]:
+    #             time.sleep(2)
+    #             status = api._get_action_status(res['action']['tag'])['status']
+    #
+    #             print("Action status: {}".format(status))
+    #
+    #         # This action will fail as the number is non-numeric
+    #         res = api._execute_action('delete-user', {'number': '125252352525asf'})
+    #
+    #         print ("Action 'delete-user response: {}".format(res))
+    #
+    #         status = res['status']
+    #         while status not in [ 'completed', 'failed' ]:
+    #             time.sleep(2)
+    #             status = api._get_action_status(res['action']['tag'])['status']
+    #
+    #             print("Action status: {}".format(status))
index 1e9397e..5068b31 100644 (file)
@@ -175,7 +175,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                           'tags': {},
                           'active': False,
                           'config': vnf_config,
-                          'vnfr_name' : agent_vnfr.name})
+                          'vnfr_name': agent_vnfr.name})
         self._log.debug("jujuCA: Charm %s for vnf %s to be deployed as %s",
                         charm, agent_vnfr.name, vnf_unique_name)
 
@@ -200,7 +200,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
             self._tasks[vnf_unique_name] = {}
 
         self._tasks[vnf_unique_name]['deploy'] = self.loop.create_task(
-            self.api.deploy_service(charm, vnf_unique_name, path=path))
+            self.api.deploy_application(charm, vnf_unique_name, path=path))
 
         self._log.debug("jujuCA: Deploying service %s",
                         vnf_unique_name)
@@ -242,7 +242,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
             self._log.debug ("jujuCA: Terminating VNFr %s, %s",
                              agent_vnfr.name, service)
             self._tasks[service]['destroy'] = self.loop.create_task(
-                    self.api.destroy_service(service)
+                    self.api.remove_application(service)
                 )
 
             del self._juju_vnfs[agent_vnfr.id]
@@ -255,7 +255,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                     tasks.append(action)
                 del tasks
         except KeyError as e:
-            self._log.debug ("jujuCA: Termiating charm service for VNFr {}, e={}".
+            self._log.debug ("jujuCA: Terminating charm service for VNFr {}, e={}".
                              format(agent_vnfr.name, e))
         except Exception as e:
             self._log.error("jujuCA: Exception terminating charm service for VNFR {}: {}".
@@ -360,10 +360,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                                             "params {} for service {}".
                                             format(params, service))
 
-                            rc = yield from self.api.apply_config(
-                                params,
-                                service=service,
-                                wait=True)
+                            rc = yield from self.api.apply_config(params, application=service)
 
                             if rc:
                                 rc = "completed"
@@ -387,9 +384,10 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                                         format(config.name, service, params))
 
                         resp = yield from self.api.execute_action(
+                            service,
                             config.name,
-                            params,
-                            service=service)
+                            **params,
+                        )
 
                         if resp:
                             if 'error' in resp:
@@ -585,12 +583,11 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                             format(vnfr_msg.name))
             return True
 
-        service = vnfr['vnf_juju_name']
-        rc = yield from self.api.is_service_up(service=service)
+        rc = yield from self.api.is_application_up(application=service)
         if not rc:
             return False
 
-        action_ids = []
+        action_ids = []
         try:
             if vnfr_msg.mgmt_interface.ip_address:
                 vnfr['tags'].update({'rw_mgmt_ip': vnfr_msg.mgmt_interface.ip_address})
@@ -633,48 +630,61 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                                 val = self.xlate(param.value,
                                                  vnfr['tags'])
                                 config.update({param.name: val})
+            except KeyError as e:
+                self._log.exception("jujuCA:(%s) Initial config error(%s): config=%s",
+                                    vnfr['vnf_juju_name'], str(e), config)
+                config = None
+                return False
+
+            if config:
+                self.juju_log('info', vnfr['vnf_juju_name'],
+                              "Applying Initial config:%s",
+                              config)
+
+                rc = yield from self.api.apply_config(
+                    config,
+                    application=service,
+                )
+                if rc is False:
+                    self.log.error("Service {} is in error state".format(service))
+                    return False
 
                         if config:
                             self.juju_log('info', vnfr['vnf_juju_name'],
                                           "Applying Initial config:%s",
                                           config)
 
-                            rc = yield from self.api.apply_config(
-                                config,
-                                service=service)
-                            if rc is False:
-                                self.log.error("Service {} is in error state".
-                                               format(service))
-                                return False
-
-                    # Apply any actions specified as part of initial config
-                    else:
-                        self._log.debug("(%s) Initial config action "
-                                        "primitive %s",
-                                        vnfr['vnf_juju_name'], primitive)
-                        action = primitive.name
-                        params = {}
-                        for param in primitive.parameter:
-                            val = self.xlate(param.value, vnfr['tags'])
-                            params.update({param.name: val})
-
-                            self._log.info("(%s) Action %s with params %s",
-                                           vnfr['vnf_juju_name'], action,
-                                           params)
-
-                        resp = yield from self.api.execute_action(
-                            action,
-                            params,
-                            service=service)
-                        if 'error' in resp:
-                            self._log.error("Applying initial config on {}"
-                                            " failed for {} with {}: {}".
-                                            format(vnfr['vnf_juju_name'],
-                                                   action, params, resp))
-                            return False
-
-                        action_ids.append(resp['action']['tag'])
+            # Apply any actions specified as part of initial config
+            for primitive in vnfr['config'].initial_config_primitive:
+                if primitive.name != 'config':
+                    self._log.debug("jujuCA:(%s) Initial config action primitive %s",
+                                    vnfr['vnf_juju_name'], primitive)
+                    action = primitive.name
+                    params = {}
+                    for param in primitive.parameter:
+                        val = self.xlate(param.value, vnfr['tags'])
+                        params.update({param.name: val})
+
+                    self._log.info("jujuCA:(%s) Action %s with params %s",
+                                   vnfr['vnf_juju_name'], action, params)
+                    self._log.debug("executing action")
+                    resp = yield from self.api.execute_action(
+                        service,
+                        action,
+                        **params,
+                    )
+                    self._log.debug("executed action")
+                    if 'error' in resp:
+                        self._log.error("Applying initial config on {} failed for {} with {}: {}".
+                                        format(vnfr['vnf_juju_name'], action, params, resp))
+                        return False
 
+                    # action_ids.append(resp['action']['tag'])
+                    # action_ids.append(resp)
+        except KeyError as e:
+            self._log.info("Juju config agent(%s): VNFR %s not managed by Juju",
+                           vnfr['vnf_juju_name'], agent_vnfr.id)
+            return False
         except Exception as e:
             self._log.exception("jujuCA:(%s) Exception juju "
                                 "apply_initial_config for VNFR {}: {}".
@@ -682,25 +692,6 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                                        agent_vnfr.id, e))
             return False
 
-        # Check if all actions completed
-        pending = True
-        while pending:
-            pending = False
-            for act in action_ids:
-                resp = yield from self.api.get_action_status(act)
-                if 'error' in resp:
-                    self._log.error("Initial config failed for action {}: {}".
-                                    format(act, resp))
-                    return False
-
-                if resp['status'] == 'failed':
-                    self._log.error("Initial config action failed for "
-                                    "action {}: {}".format(act, resp))
-                    return False
-
-                if resp['status'] == 'pending':
-                    pending = True
-
         return True
 
     def add_vnfr_managed(self, agent_vnfr):
@@ -728,7 +719,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
 
             vnfr = self._juju_vnfs[vnfr_id].vnfr
             service = vnfr['vnf_juju_name']
-            resp = self.api.is_service_active(service=service)
+            resp = self.api.is_application_active(application=service)
             self._juju_vnfs[vnfr_id]['active'] = resp
             self._log.debug("jujuCA: Service state for {} is {}".
                             format(service, resp))
@@ -761,7 +752,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
             return rc
 
         try:
-            resp = yield from self.api.get_service_status(service=service)
+            resp = yield from self.api.get_service_status(application=service)
             self._log.debug("jujuCA: Get service %s status? %s", service, resp)
 
             if resp == 'error':