from juju.controller import Controller
from juju.model import ModelObserver
-from juju.errors import JujuAPIError
+from juju.errors import JujuAPIError, JujuError
# We might need this to connect to the websocket securely, but test and verify.
try:
"""Something failed while attempting to execute a primitive."""
+class NetworkServiceDoesNotExist(Exception):
+ """The Network Service being acted against does not exist."""
+
+
# Quiet the debug logging
logging.getLogger('websockets.protocol').setLevel(logging.INFO)
logging.getLogger('juju.client.connection').setLevel(logging.WARN)
'rw_mgmt_ip': '1.2.3.4',
# Pass the initial-config-primitives section of the vnf or vdu
'initial-config-primitives': {...}
+ 'user_values': dictionary with the day-1 parameters provided at instantiation time. It will replace values
+ inside < >. rw_mgmt_ip will be included here also
}
:param dict machine_spec: A dictionary describing the machine to
install to
else:
seq = primitive['seq']
- params = {}
+ params_ = {}
if 'parameter' in primitive:
- params = primitive['parameter']
+ params_ = primitive['parameter']
+
+ user_values = params.get("user_values", {})
+ if 'rw_mgmt_ip' not in user_values:
+ user_values['rw_mgmt_ip'] = None
+ # just for backward compatibility, because it will be provided always by modern version of LCM
primitives[seq] = {
'name': primitive['name'],
'parameters': self._map_primitive_parameters(
- params,
- {'<rw_mgmt_ip>': None}
+ params_,
+ user_values
),
}
'initial-config-primitives': {...}
}
"""
- self.log.debug("Executing {}".format(primitive))
+ self.log.debug("Executing primitive={} params={}".format(primitive, params))
uuid = None
try:
if not self.authenticated:
await self.disconnect_model(self.monitors[model_name])
- # Notify the callback that this charm has been removed.
self.notify_callback(
model_name,
application_name,
"removed",
+ "Removing charm {}".format(application_name),
callback,
*callback_args,
)
self.log.debug(e)
raise e
- async def DestroyNetworkService(self, nsd):
- raise NotImplementedError()
+ async def CreateNetworkService(self, ns_uuid):
+ """Create a new Juju model for the Network Service.
+
+ Creates a new Model in the Juju Controller.
+
+ :param str ns_uuid: A unique id representing an instaance of a
+ Network Service.
+
+ :returns: True if the model was created. Raises JujuError on failure.
+ """
+ if not self.authenticated:
+ await self.login()
+
+ models = await self.controller.list_models()
+ if ns_uuid not in models:
+ try:
+ self.models[ns_uuid] = await self.controller.add_model(
+ ns_uuid
+ )
+ except JujuError as e:
+ if "already exists" not in e.message:
+ raise e
+ return True
+
+ async def DestroyNetworkService(self, ns_uuid):
+ """Destroy a Network Service.
+
+ Destroy the Network Service and any deployed charms.
+
+ :param ns_uuid The unique id of the Network Service
+
+ :returns: True if the model was created. Raises JujuError on failure.
+ """
+
+ # Do not delete the default model. The default model was used by all
+ # Network Services, prior to the implementation of a model per NS.
+ if ns_uuid.lower() == "default":
+ return False
+
+ if not self.authenticated:
+ self.log.debug("Authenticating with Juju")
+ await self.login()
+
+ # Disconnect from the Model
+ if ns_uuid in self.models:
+ await self.disconnect_model(self.models[ns_uuid])
+
+ try:
+ await self.controller.destroy_models(ns_uuid)
+ except JujuError:
+ raise NetworkServiceDoesNotExist(
+ "The Network Service '{}' does not exist".format(ns_uuid)
+ )
+
+ return True
async def GetMetrics(self, model_name, application_name):
"""Get the metrics collected by the VCA.
return config
- def _map_primitive_parameters(self, parameters, values):
+ def _map_primitive_parameters(self, parameters, user_values):
params = {}
for parameter in parameters:
param = str(parameter['name'])
- value = None
+ value = parameter.get('value')
+
+ # map parameters inside a < >; e.g. <rw_mgmt_ip>. with the provided user_values.
+ # Must exist at user_values except if there is a default value
+ if isinstance(value, str) and value.startswith("<") and value.endswith(">"):
+ if parameter['value'][1:-1] in user_values:
+ value = user_values[parameter['value'][1:-1]]
+ elif 'default-value' in parameter:
+ value = parameter['default-value']
+ else:
+ raise KeyError("parameter {}='{}' not supplied ".format(param, value))
# If there's no value, use the default-value (if set)
- if parameter['value'] is None and 'default-value' in parameter:
+ if value is None and 'default-value' in parameter:
value = parameter['default-value']
# Typecast parameter value, if present
- if 'data-type' in parameter:
- paramtype = str(parameter['data-type']).lower()
+ paramtype = "string"
+ try:
+ if 'data-type' in parameter:
+ paramtype = str(parameter['data-type']).lower()
- if paramtype == "integer":
- value = int(parameter['value'])
- elif paramtype == "boolean":
- value = bool(parameter['value'])
+ if paramtype == "integer":
+ value = int(value)
+ elif paramtype == "boolean":
+ value = bool(value)
+ else:
+ value = str(value)
else:
- value = str(parameter['value'])
- else:
- # If there's no data-type, assume the value is a string
- value = str(parameter['value'])
+ # If there's no data-type, assume the value is a string
+ value = str(value)
+ except ValueError:
+ raise ValueError("parameter {}='{}' cannot be converted to type {}".format(param, value, paramtype))
- if parameter['value'] == "<rw_mgmt_ip>":
- params[param] = str(values[parameter['value']])
- else:
- params[param] = value
+ params[param] = value
return params
def _get_config_from_yang(self, config_primitive, values):
elif not c.isalpha():
c = "-"
appname += c
- return re.sub('\-+', '-', appname.lower())
+ return re.sub('-+', '-', appname.lower())
# def format_application_name(self, nsd_name, vnfr_name, member_vnf_index=0):
# """Format the name of the application
models = await self.controller.list_models()
if model_name not in models:
- self.models[model_name] = await self.controller.add_model(
- model_name
- )
+ try:
+ self.models[model_name] = await self.controller.add_model(
+ model_name
+ )
+ except JujuError as e:
+ if "already exists" not in e.message:
+ raise e
else:
self.models[model_name] = await self.controller.get_model(
model_name
async def logout(self):
"""Logout of the Juju controller."""
if not self.authenticated:
- return
+ return False
try:
for model in self.models:
"Fatal error logging out of Juju Controller: {}".format(e)
)
raise e
+ return True
async def disconnect_model(self, model):
self.log.debug("Disconnecting model {}".format(model))
if model in self.models:
- print(self.models[model].applications)
- if len(self.models[model].applications) == 0:
- print("Destroying empty model")
- await self.controller.destroy_models(model)
-
print("Disconnecting model")
await self.models[model].disconnect()
self.refcount['model'] -= 1