--- /dev/null
+############################################################################
+# Copyright 2016 RIFT.io Inc #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License"); #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http://www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+import argparse
+import asyncio
+import logging
+import os
+import ssl
+
+import juju.loop
+from juju.controller import Controller
+from juju.model import Model, ModelObserver
+
+try:
+ ssl._create_default_https_context = ssl._create_unverified_context
+except AttributeError:
+ # Legacy Python doesn't verify by default (see pep-0476)
+ # https://www.python.org/dev/peps/pep-0476/
+ pass
+
+
+class JujuVersionError(Exception):
+ pass
+
+
+class JujuApiError(Exception):
+ pass
+
+
+class JujuEnvError(JujuApiError):
+ pass
+
+
+class JujuModelError(JujuApiError):
+ pass
+
+
+class JujuStatusError(JujuApiError):
+ pass
+
+
+class JujuUnitsError(JujuApiError):
+ pass
+
+
+class JujuWaitUnitsError(JujuApiError):
+ pass
+
+
+class JujuSrvNotDeployedError(JujuApiError):
+ pass
+
+
+class JujuAddCharmError(JujuApiError):
+ pass
+
+
+class JujuDeployError(JujuApiError):
+ pass
+
+
+class JujuDestroyError(JujuApiError):
+ pass
+
+
+class JujuResolveError(JujuApiError):
+ pass
+
+
+class JujuActionError(JujuApiError):
+ pass
+
+
+class JujuActionApiError(JujuActionError):
+ pass
+
+
+class JujuActionInfoError(JujuActionError):
+ pass
+
+
+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.
+
+ 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__)
+
+ # Quiet websocket traffic
+ logging.getLogger('websockets.protocol').setLevel(logging.INFO)
+
+ self.log.debug('JujuApi: instantiated')
+
+ self.server = server
+ self.port = port
+
+ self.secret = secret
+ if user.startswith('user-'):
+ self.user = user
+ else:
+ self.user = 'user-{}'.format(user)
+
+ 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 add_relation(self, a, b, via=None):
+ """
+ Add a relation between two application endpoints.
+
+ :param a An application endpoint
+ :param b An application endpoint
+ :param via The egress subnet(s) for outbound traffic, e.g., (192.168.0.0/16,10.0.0.0/8)
+ """
+ if not self.authenticated:
+ await self.login()
+
+ m = await self.get_model()
+ try:
+ m.add_relation(a, b, via)
+ finally:
+ await m.disconnect()
+
+ async def apply_config(self, config, application):
+ """Apply a configuration to the application."""
+ self.log.debug("JujuApi: Applying configuration to {}.".format(
+ application
+ ))
+ return await self.set_config(application=application, config=config)
+
+ async def deploy_application(self, charm, name="", path="", specs={}):
+ """
+ Deploy an application.
+
+ Deploy an application to a container or a machine already provisioned
+ by the OSM Resource Orchestrator (requires the Juju public ssh key
+ installed on the new machine via cloud-init).
+
+ :param str charm: The name of the charm
+ :param str name: The name of the application, if different than the charm
+ :param str path: The path to the charm
+ :param dict machine: A dictionary identifying the machine to manage via Juju
+ Examples::
+
+ deploy_application(..., specs={'host': '10.0.0.4', 'user': 'ubuntu'})
+ """
+ if not self.authenticated:
+ await self.login()
+
+ # Check that the charm is valid and exists.
+ if charm is None:
+ return None
+
+ app = await self.get_application(name)
+ if app is None:
+
+ # Check for specific machine placement
+ to = None
+ if all(k in specs for k in ['hostname', 'username']):
+ machine = await self.model.add_machine(spec='ssh:%@%'.format(
+ specs['host'],
+ specs['user'],
+ ))
+ to = machine.id
+
+ # TODO: Handle the error if the charm isn't found.
+ self.log.debug("JujuApi: Deploying charm {} ({}) from {}".format(
+ charm,
+ name,
+ path,
+ to=to,
+ ))
+ 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()
+
+ self.log.debug("JujuApi: Waiting for status of action uuid {}".format(uuid))
+ 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()
+
+ self.log.debug("JujuApi: Getting application {}".format(application))
+ app = None
+ if application and self.model:
+ if self.model.applications:
+ if application in self.model.applications:
+ app = self.model.applications[application]
+ return app
+
+ async def get_application_status(self, application):
+ """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
+ self.log.debug("JujuApi: Status of application {} is {}".format(
+ application,
+ str(status),
+ ))
+ return status
+ get_service_status = get_application_status
+
+ async def get_config(self, application):
+ """Get the configuration of an application."""
+ if not self.authenticated:
+ await self.login()
+
+ config = None
+ app = await self.get_application(application)
+ if app:
+ config = await app.get_config()
+
+ self.log.debug("JujuApi: Config of application {} is {}".format(
+ application,
+ str(config),
+ ))
+
+ return config
+
+ async def get_model(self, name='default'):
+ """Get a model from the Juju Controller.
+
+ 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)
+
+ self.log.debug("JujuApi: Connecting to model {} ({})".format(
+ model,
+ uuid,
+ ))
+
+ await model.connect(
+ self.endpoint,
+ uuid,
+ self.user,
+ self.secret,
+ None,
+ )
+
+ return model
+
+ async def get_model_uuid(self, name='default'):
+ """Get the UUID of a model.
+
+ Iterate through all models in a controller and find the matching model.
+ """
+ if not self.authenticated:
+ await self.login()
+
+ uuid = None
+
+ models = await self.controller.get_models()
+
+ self.log.debug("JujuApi: Looking through {} models for model {}".format(
+ len(models.user_models),
+ name,
+ ))
+ for model in models.user_models:
+ if model.model.name == name:
+ uuid = model.model.uuid
+ break
+
+ return uuid
+
+ async def get_status(self):
+ """Get the model status."""
+ if not self.authenticated:
+ await self.login()
+
+ if not self.model:
+ self.model = self.get_model(self.model_name)
+
+ class model_state:
+ applications = {}
+ machines = {}
+ relations = {}
+
+ self.log.debug("JujuApi: Getting model status")
+ status = model_state()
+ status.applications = self.model.applications
+ status.machines = self.model.machines
+
+ return status
+
+ 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
+
+ self.log.debug("JujuApi: Application {} is {} active".format(
+ application,
+ "" if status else "not",
+ ))
+
+ 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
+
+ self.log.debug("JujuApi: Application {} is {} blocked".format(
+ application,
+ "" if status else "not",
+ ))
+
+ 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
+ self.log.debug("JujuApi: Application {} is {} deployed".format(
+ application,
+ "" if status else "not",
+ ))
+
+ 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
+ self.log.debug("JujuApi: Application {} is {} errored".format(
+ application,
+ "" if status else "not",
+ ))
+
+ 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
+ self.log.debug("JujuApi: Application {} is {} in maintenence".format(
+ application,
+ "" if status else "not",
+ ))
+
+ 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
+ self.log.debug("JujuApi: Application {} is {} up".format(
+ application,
+ "" if status else "not",
+ ))
+ 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()
+
+ self.log.debug("JujuApi: Logging into controller")
+
+ if self.secret:
+ await self.controller.connect(
+ self.endpoint,
+ self.user,
+ self.secret,
+ cacert,
+ )
+ else:
+ await self.controller.connect_current()
+
+ self.authenticated = True
+ self.model = await self.get_model(self.model_name)
+
+ async def logout(self):
+ """Logout of the Juju controller."""
+ if not self.authenticated:
+ return
+
+ 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:
+ self.log.debug("JujuApi: Destroying application {}".format(
+ name,
+ ))
+
+ await app.destroy()
+
+ async def remove_relation(self, a, b):
+ """
+ Remove a relation between two application endpoints
+
+ :param a An application endpoint
+ :param b An application endpoint
+ """
+ if not self.authenticated:
+ await self.login()
+
+ m = await self.get_model()
+ try:
+ m.remove_relation(a, b)
+ finally:
+ await m.disconnect()
+
+ async def resolve_error(self, application=None):
+ """Resolve units in error state."""
+ if not self.authenticated:
+ await self.login()
+
+ app = await self.get_application(application)
+ if app:
+ self.log.debug("JujuApi: Resolving errors for application {}".format(
+ application,
+ ))
+
+ 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]
+
+ self.log.debug("JujuApi: Running Action {} against Application {}".format(
+ action_name,
+ application,
+ ))
+
+ action = await unit.run_action(action_name, **params)
+
+ # Wait for the action to complete
+ await action.wait()
+
+ result['status'] = action.status
+ result['action']['tag'] = action.data['id']
+ result['action']['results'] = action.results
+
+ 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()
+
+ app = await self.get_application(application)
+ if app:
+ self.log.debug("JujuApi: Setting config for Application {}".format(
+ application,
+ ))
+ await app.set_config(config)
+
+ # Verify the config is set
+ newconf = await app.get_config()
+ for key in config:
+ if config[key] != newconf[key]:
+ 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 wait_for_application(self, name, timeout=300):
+ """Wait for an application to become active."""
+ if not self.authenticated:
+ await self.login()
+
+ app = await self.get_application(name)
+ if app:
+ self.log.debug("JujuApi: Waiting {} seconds for Application {}".format(
+ timeout,
+ name,
+ ))
+
+ 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,
+ )
+
+
+def get_argparser():
+ parser = argparse.ArgumentParser(description='Test Driver for Juju API')
+
+ ###################
+ # Authentication #
+ ###################
+ 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(
+ "-m", "--model",
+ default='default',
+ help="The model to connect to."
+ )
+
+ ##########
+ # Charm #
+ ##########
+ parser.add_argument(
+ "-d", "--directory",
+ help="Local directory for the charm"
+ )
+ parser.add_argument(
+ "--application",
+ help="Charm name"
+ )
+
+ #############
+ # Placement #
+ #############
+
+ """
+ To deploy to a non-Juju machine, provide the host and
+ credentials for Juju to manually provision (host, username, (password or key?))
+
+ """
+ parser.add_argument(
+ "--proxy",
+ action='store_true',
+ help="Deploy as a proxy charm.",
+ )
+ parser.add_argument(
+ "--no-proxy",
+ action='store_false',
+ dest='proxy',
+ help="Deploy as a full charm.",
+ )
+ parser.set_defaults(proxy=True)
+
+ # Test options?
+ # unit test?
+ #######
+ # VNF #
+ #######
+
+ return parser.parse_args()
+
+
+async def deploy_charm_and_wait():
+ args = get_argparser()
+
+ # Set logging level to debug so we can see verbose output from the
+ # juju library.
+ logging.basicConfig(level=logging.DEBUG)
+
+ # 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)
+
+ """Here's an example of a coroutine that will deploy a charm and wait until
+ it's ready to be used."""
+ api = JujuApi(server=args.server,
+ port=args.port,
+ user=args.user,
+ secret=args.password,
+ # loop=loop,
+ log=ws_logger,
+ model_name=args.model
+ )
+ print("Logging in...")
+ await api.login()
+
+ if api.authenticated:
+ status = await api.get_status()
+ print('Applications:', list(status.applications.keys()))
+ print('Machines:', list(status.machines.keys()))
+
+ if args.directory and args.application:
+
+
+
+ # Deploy the charm
+ charm = os.path.basename(
+ os.path.expanduser(
+ os.path.dirname(args.directory)
+ )
+ )
+ await api.deploy_application(charm,
+ name=args.application,
+ path=args.directory,
+ )
+
+ # Wait for the application to fully deploy. This will block until the
+ # agent is in an idle state, and the charm's workload is either
+ # 'active' or 'unknown', meaning it's ready but the author did not
+ # explicitly set a workload state.
+ print("Waiting for application '{}' to deploy...".format(charm))
+ while (True):
+ # Deploy the charm and wait, periodically checking its status
+ await api.wait_for_application(charm, 30)
+
+ error = await api.is_application_error(charm)
+ if error:
+ print("This application is in an error state.")
+ break
+
+ blocked = await api.is_application_blocked(charm)
+ if blocked:
+ print("This application is blocked.")
+ break
+
+ # An extra check to see if the charm is ready
+ up = await api.is_application_up(charm)
+ print("Application is {}".format("up" if up else "down"))
+
+ print("Service {} is deployed".format(args.application))
+
+ ###################################
+ # Execute config on a proxy charm #
+ ###################################
+ config = await api.get_config(args.application)
+ hostname = config['ssh-username']['value']
+ rhostname = hostname[::-1]
+
+ # Apply the configuration
+ await api.apply_config(
+ {'ssh-username': rhostname}, application=args.application
+ )
+
+ # Get the configuration
+ config = await api.get_config(args.application)
+
+ # Verify the configuration has been updated
+ assert(config['ssh-username']['value'] == rhostname)
+
+ ####################################
+ # Get the status of an application #
+ ####################################
+ status = await api.get_application_status(charm)
+ print("Application Status: {}".format(status))
+
+ ###########################
+ # Execute a simple action #
+ ###########################
+ result = await 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 = await api.run_action(charm, 'run', command='hostname')
+
+ print("Action {} status is {} and returned {}".format(
+ result['status'],
+ result['action']['tag'],
+ result['action']['results']
+ ))
+
+ print("Logging out...")
+ await api.logout()
+ api = None
+
+# get public key in juju controller? that can be pulled without need of a charm deployed and installed to vm via cloud-init
+
+if __name__ == "__main__":
+ # Create a single event loop for running code asyncronously.
+ loop = asyncio.get_event_loop()
+
+ # An initial set of tasks to run
+ tasks = [
+ deploy_charm_and_wait(),
+ ]
+
+ # TODO: optionally run forever and use a Watcher to monitor what's happening
+ loop.run_until_complete(asyncio.wait(tasks))
--- /dev/null
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+from juju_api import JujuApi
+from juju.model import ModelObserver
+import logging
+import os
+import os.path
+import re
+
+
+class VCAMonitor(ModelObserver):
+ """Monitor state changes within the Juju Model."""
+ context = None
+
+ async def on_change(self, delta, old, new, model):
+ """React to changes in the Juju model."""
+ status = None
+ db_nsr = self.context['db_nsr']
+ vnf_id = self.context['vnf_id']
+
+ nsr_lcm = db_nsr["_admin"]["deploy"]
+ nsr_id = nsr_lcm["id"]
+ application = self.context['application']
+
+ if delta.entity == "unit":
+ # We only care about changes to a unit
+ if delta.type == "add" and old is None:
+ if new and new.application == application:
+ status = "BUILD"
+ elif delta.type == "change":
+ if new and new.application == application:
+ if new.agent_status == "idle":
+ if new.workload_status in ("active", "blocked"):
+ status = "ACTIVE"
+
+ elif delta.type == "remove" and new is None:
+ if new and new.application == application:
+ status = "DELETING"
+
+ if status:
+ nsr_lcm["VCA"][vnf_id]['operational-status'] = status
+
+ # TODO: Clean this up, and make it work with deletes (if we need
+ # TODO: to update the database post-delete)
+ # Figure out if we're finished configuring
+ count = len(nsr_lcm["VCA"])
+ active = 0
+ for vnf_id in nsr_lcm["VCA"]:
+ if nsr_lcm["VCA"][vnf_id]['operational-status'] == "ACTIVE":
+ active += 1
+ if active == count:
+ db_nsr["config-status"] = "done"
+ else:
+ db_nsr["config-status"] = "configuring {}/{}".format(active, count)
+
+ try:
+ self.context['db'].replace(
+ "nsrs",
+ nsr_id,
+ db_nsr
+ )
+
+ # self.context['db'].replace(
+ # "nsr_lcm",
+ # {"id": self.context['nsr_lcm']['id']},
+ # self.context['nsr_lcm']
+ # )
+ except Exception as e:
+ # I've seen this happen when we handle a delete, because the
+ # db record is gone by the time we've finished deleting
+ # the charms.
+ print("Error updating database: ", e)
+
+ pass
+
+
+def GetJujuApi(config):
+ # Quiet logging from the websocket library. If you want to see
+ # everything sent over the wire, set this to DEBUG.
+ logging.basicConfig(level=logging.DEBUG)
+
+ ws_logger = logging.getLogger('websockets.protocol')
+ ws_logger.setLevel(logging.INFO)
+
+ api = JujuApi(server=config['host'],
+ port=config['port'],
+ user=config['user'],
+ secret=config['secret'],
+ log=ws_logger,
+ model_name='default'
+ )
+ return api
+
+
+def get_vnf_unique_name(nsr_name, vnfr_name, member_vnf_index):
+ """Get the unique VNF name.
+ Charm names accepts only a to z and non-consecutive - characters."""
+ name = "{}-{}-{}".format(nsr_name, vnfr_name, member_vnf_index)
+ new_name = ''
+ for c in name:
+ if c.isdigit():
+ c = chr(97 + int(c))
+ elif not c.isalpha():
+ c = "-"
+ new_name += c
+ return re.sub('\-+', '-', new_name.lower())
+
+
+def get_initial_config(initial_config_primitive, mgmt_ip):
+ config = {}
+ for primitive in initial_config_primitive:
+ if primitive['name'] == 'config':
+ for parameter in primitive['parameter']:
+ param = parameter['name']
+ if parameter['value'] == "<rw_mgmt_ip>":
+ config[param] = mgmt_ip
+ else:
+ config[param] = parameter['value']
+ return config
+
+
+async def DeployApplication(vcaconfig, db, db_nsr, vnfd,
+ vnfd_index, charm_path):
+ """
+ Deploy a charm.
+
+ Deploy a VNF configuration charm from a local directory.
+ :param dict vcaconfig: The VCA portion of the LCM Configuration
+ :param object vnfd: The VNF descriptor
+ ...
+ :param int vnfd_index: The index of the vnf.
+
+ :Example:
+
+ DeployApplication(...)
+ """
+ nsr_lcm = db_nsr["_admin"]["deploy"]
+ nsr_id = nsr_lcm["id"]
+ vnf_id = vnfd['id']
+
+ if "proxy" in vnfd["vnf-configuration"]["juju"]:
+ use_proxy = vnfd["vnf-configuration"]["juju"]["proxy"]
+ else:
+ # TBD: We need this to handle a full charm
+ use_proxy = True
+
+ application = get_vnf_unique_name(
+ db_nsr["name"].lower().strip(),
+ vnfd['id'],
+ vnfd_index,
+ )
+
+ api = GetJujuApi(vcaconfig)
+
+ await api.login()
+ if api.authenticated:
+ charm = os.path.basename(charm_path)
+
+ # Set the INIT state; further operational status updates
+ # will be made by the VCAMonitor
+ nsr_lcm["VCA"][vnf_id] = {}
+ nsr_lcm["VCA"][vnf_id]['operational-status'] = 'INIT'
+ nsr_lcm["VCA"][vnf_id]['application'] = application
+
+ db.replace("nsrs", nsr_id, db_nsr)
+
+ model = await api.get_model()
+ context = {
+ 'application': application,
+ 'vnf_id': vnf_id,
+ 'db_nsr': db_nsr,
+ 'db': db,
+ }
+ mon = VCAMonitor()
+ mon.context = context
+ model.add_observer(mon)
+
+ await api.deploy_application(charm,
+ name=application,
+ path=charm_path,
+ )
+
+ # Get and apply the initial config primitive
+ cfg = get_initial_config(
+ vnfd["vnf-configuration"].get(
+ "initial-config-primitive"
+ ),
+ nsr_lcm['nsr_ip'][vnfd_index]
+ )
+
+ await api.apply_config(cfg, application)
+
+ await api.logout()
+
+
+async def RemoveApplication(vcaconfig, db, db_nsr, vnfd, vnfd_index):
+ """
+ Remove an application from the Juju Controller
+
+ Removed the named application and it's charm from the Juju controller.
+
+ :param object loop: The event loop.
+ :param str application_name: The unique name of the application.
+
+ :Example:
+
+ RemoveApplication(loop, "ping_vnf")
+ RemoveApplication(loop, "pong_vnf")
+ """
+ nsr_lcm = db_nsr["_admin"]["deploy"]
+ vnf_id = vnfd['id']
+ application = nsr_lcm["VCA"][vnf_id]['application']
+
+ api = GetJujuApi(vcaconfig)
+
+ await api.login()
+ if api.authenticated:
+ model = await api.get_model()
+ context = {
+ 'application': application,
+ 'vnf_id': vnf_id,
+ 'db_nsr': db_nsr,
+ 'db': db,
+ }
+
+ mon = VCAMonitor()
+ mon.context = context
+ model.add_observer(mon)
+
+ print("VCA: Removing application {}".format(application))
+ await api.remove_application(application)
+ await api.logout()