N2VCException,
N2VCConnectionException,
N2VCExecutionException,
+ N2VCApplicationExists,
+ JujuApplicationExists,
# N2VCNotFound,
MethodNotImplemented,
)
from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
from n2vc.libjuju import Libjuju
from n2vc.store import MotorStore
+from n2vc.utils import generate_random_alfanum_string
from n2vc.vca.connection import get_connection
+from retrying_async import retry
class N2VCJujuConnector(N2VCConnector):
db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
self._store = MotorStore(db_uri)
self.loading_libjuju = asyncio.Lock(loop=self.loop)
-
+ self.delete_namespace_locks = {}
self.log.info("N2VC juju connector initialized")
async def get_status(
libjuju = await self._get_libjuju(vca_id)
for model_name in vcastatus:
# Adding executed actions
- vcastatus[model_name]["executedActions"] = \
- await libjuju.get_executed_actions(model_name)
+ vcastatus[model_name][
+ "executedActions"
+ ] = await libjuju.get_executed_actions(model_name)
for application in vcastatus[model_name]["applications"]:
# Adding application actions
- vcastatus[model_name]["applications"][application]["actions"] = \
- await libjuju.get_actions(application, model_name)
+ vcastatus[model_name]["applications"][application][
+ "actions"
+ ] = await libjuju.get_actions(application, model_name)
# Adding application configs
- vcastatus[model_name]["applications"][application]["configs"] = \
- await libjuju.get_application_configs(model_name, application)
+ vcastatus[model_name]["applications"][application][
+ "configs"
+ ] = await libjuju.get_application_configs(model_name, application)
except Exception as e:
self.log.debug("Error in updating vca status: {}".format(str(e)))
return ee_id
+ # In case of native_charm is being deployed, if JujuApplicationExists error happens
+ # it will try to add_unit
+ @retry(attempts=3, delay=5, retry_exceptions=(N2VCApplicationExists,), timeout=None)
async def install_configuration_sw(
self,
ee_id: str,
config: dict = None,
num_units: int = 1,
vca_id: str = None,
+ scaling_out: bool = False,
+ vca_type: str = None,
):
"""
Install the software inside the execution environment identified by ee_id
:param: config: Dictionary with deployment config information.
:param: num_units: Number of units to deploy of a particular charm.
:param: vca_id: VCA ID
+ :param: scaling_out: Boolean to indicate if it is a scaling out operation
+ :param: vca_type: VCA type
"""
self.log.info(
artifact_path = artifact_path.replace("//", "/")
# check charm path
- if not self.fs.file_exists(artifact_path, mode="dir"):
+ if not self.fs.file_exists(artifact_path):
msg = "artifact path does not exist: {}".format(artifact_path)
raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
full_path = self.fs.path + "/" + artifact_path
try:
- await libjuju.deploy_charm(
- model_name=model_name,
- application_name=application_name,
- path=full_path,
- machine_id=machine_id,
- db_dict=db_dict,
- progress_timeout=progress_timeout,
- total_timeout=total_timeout,
- config=config,
- num_units=num_units,
+ if vca_type == "native_charm" and await libjuju.check_application_exists(
+ model_name, application_name
+ ):
+ await libjuju.add_unit(
+ application_name=application_name,
+ model_name=model_name,
+ machine_id=machine_id,
+ db_dict=db_dict,
+ progress_timeout=progress_timeout,
+ total_timeout=total_timeout,
+ )
+ else:
+ await libjuju.deploy_charm(
+ model_name=model_name,
+ application_name=application_name,
+ path=full_path,
+ machine_id=machine_id,
+ db_dict=db_dict,
+ progress_timeout=progress_timeout,
+ total_timeout=total_timeout,
+ config=config,
+ num_units=num_units,
+ )
+ except JujuApplicationExists as e:
+ raise N2VCApplicationExists(
+ message="Error deploying charm into ee={} : {}".format(ee_id, e.message)
)
except Exception as e:
raise N2VCException(
- message="Error desploying charm into ee={} : {}".format(ee_id, e)
+ message="Error deploying charm into ee={} : {}".format(ee_id, e)
)
self.log.info("Configuration sw installed")
artifact_path = artifact_path.replace("//", "/")
# check charm path
- if not self.fs.file_exists(artifact_path, mode="dir"):
+ if not self.fs.file_exists(artifact_path):
msg = "artifact path does not exist: {}".format(artifact_path)
raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
:param: vca_id: VCA ID
"""
self.log.info("Deleting namespace={}".format(namespace))
- libjuju = await self._get_libjuju(vca_id)
+ will_not_delete = False
+ if namespace not in self.delete_namespace_locks:
+ self.delete_namespace_locks[namespace] = asyncio.Lock(loop=self.loop)
+ delete_lock = self.delete_namespace_locks[namespace]
- # check arguments
- if namespace is None:
- raise N2VCBadArgumentsException(
- message="namespace is mandatory", bad_args=["namespace"]
- )
+ while delete_lock.locked():
+ will_not_delete = True
+ await asyncio.sleep(0.1)
- _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
- namespace=namespace
- )
- if ns_id is not None:
- try:
- models = await libjuju.list_models(contains=ns_id)
- for model in models:
- await libjuju.destroy_model(
- model_name=model, total_timeout=total_timeout
+ if will_not_delete:
+ self.log.info("Namespace {} deleted by another worker.".format(namespace))
+ return
+
+ try:
+ async with delete_lock:
+ libjuju = await self._get_libjuju(vca_id)
+
+ # check arguments
+ if namespace is None:
+ raise N2VCBadArgumentsException(
+ message="namespace is mandatory", bad_args=["namespace"]
)
- except Exception as e:
- raise N2VCException(
- message="Error deleting namespace {} : {}".format(namespace, e)
- )
- else:
- raise N2VCBadArgumentsException(
- message="only ns_id is permitted to delete yet", bad_args=["namespace"]
- )
+ (
+ _nsi_id,
+ ns_id,
+ _vnf_id,
+ _vdu_id,
+ _vdu_count,
+ ) = self._get_namespace_components(namespace=namespace)
+ if ns_id is not None:
+ try:
+ models = await libjuju.list_models(contains=ns_id)
+ for model in models:
+ await libjuju.destroy_model(
+ model_name=model, total_timeout=total_timeout
+ )
+ except Exception as e:
+ raise N2VCException(
+ message="Error deleting namespace {} : {}".format(
+ namespace, e
+ )
+ )
+ else:
+ raise N2VCBadArgumentsException(
+ message="only ns_id is permitted to delete yet",
+ bad_args=["namespace"],
+ )
+ finally:
+ self.delete_namespace_locks.pop(namespace)
self.log.info("Namespace {} deleted".format(namespace))
async def delete_execution_environment(
db_dict: dict = None,
total_timeout: float = None,
scaling_in: bool = False,
+ vca_type: str = None,
vca_id: str = None,
):
"""
e.g. {collection: "nsrs", filter:
{_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
:param: total_timeout: Total timeout
- :param: scaling_in: Boolean to indicate if is it a scaling in operation
+ :param: scaling_in: Boolean to indicate if it is a scaling in operation
+ :param: vca_type: VCA type
:param: vca_id: VCA ID
"""
self.log.info("Deleting execution environment ee_id={}".format(ee_id))
message="ee_id is mandatory", bad_args=["ee_id"]
)
- model_name, application_name, _machine_id = self._get_ee_id_components(
+ model_name, application_name, machine_id = self._get_ee_id_components(
ee_id=ee_id
)
try:
if not scaling_in:
# destroy the model
- # TODO: should this be removed?
await libjuju.destroy_model(
model_name=model_name,
total_timeout=total_timeout,
)
+ elif vca_type == "native_charm" and scaling_in:
+ # destroy the unit in the application
+ await libjuju.destroy_unit(
+ application_name=application_name,
+ model_name=model_name,
+ machine_id=machine_id,
+ total_timeout=total_timeout,
+ )
else:
# destroy the application
await libjuju.destroy_application(
progress_timeout: float = None,
total_timeout: float = None,
vca_id: str = None,
+ vca_type: str = None,
) -> str:
"""
Execute a primitive in the execution environment
:param: progress_timeout: Progress timeout
:param: total_timeout: Total timeout
:param: vca_id: VCA ID
+ :param: vca_type: VCA type
:returns str: primitive result, if ok. It raises exceptions in case of fail
"""
(
model_name,
application_name,
- _machine_id,
+ machine_id,
) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
+ # To run action on the leader unit in libjuju.execute_action function,
+ # machine_id must be set to None if vca_type is not native_charm
+ if vca_type != "native_charm":
+ machine_id = None
except Exception:
raise N2VCBadArgumentsException(
message="ee_id={} is not a valid execution environment id".format(
application_name=application_name,
action_name=primitive_name,
db_dict=db_dict,
+ machine_id=machine_id,
progress_timeout=progress_timeout,
total_timeout=total_timeout,
**params_dict
"""
Build application name from namespace
:param namespace:
- :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
+ :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>-<random_value>
"""
# TODO: Enforce the Juju 50-character application limit
else:
vdu_count = "-cnt-" + vdu_count
- application_name = "app-{}{}{}".format(vnf_id, vdu_id, vdu_count)
+ # Generate a random suffix with 5 characters (the default size used by K8s)
+ random_suffix = generate_random_alfanum_string(size=5)
+
+ application_name = "app-{}{}{}-{}".format(
+ vnf_id, vdu_id, vdu_count, random_suffix
+ )
return N2VCJujuConnector._format_app_name(application_name)