blob: c84713aa5a3bd6ae362f6997a571f56a2e10bc86 [file] [log] [blame]
#######################################################################################
# Copyright ETSI Contributors and Others.
#
# 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 logging
from temporalio import activity
from time import time
from n2vc.temporal_libjuju import ConnectionInfo, Libjuju
from osm_common.temporal_constants import (
ACTIVITY_DELETE_VIM,
ACTIVITY_TEST_VIM_CONNECTIVITY,
ACTIVITY_UPDATE_VIM_OPERATION_STATE,
ACTIVITY_UPDATE_VIM_STATE,
ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST,
ACTIVITY_DEPLOY_CHARM,
ACTIVITY_CHECK_CHARM_STATUS,
)
from osm_common.dataclasses.temporal_dataclasses import (
DeleteVimInput,
TestVimConnectivityInput,
UpdateVimOperationStateInput,
UpdateVimStateInput,
CreateModelInput,
VduInstantiateInput,
)
activity.logger = logging.getLogger("lcm.temporal.vim_activities")
class JujuPaasConnector:
"""Handles Juju Controller operations.
Args:
db (object): Data Access Object
"""
def __init__(self, db):
self.db = db
def _decrypt_password(self, vim_content: dict) -> str:
"""Decrypt a password.
vim_content (dict): VIM details as a dictionary
Returns:
plain text password (str)
"""
return self.db.decrypt(
vim_content["vim_password"],
schema_version=vim_content["schema_version"],
salt=vim_content["_id"],
)
def _get_connection_info(self, vim_id: str) -> ConnectionInfo:
"""Get VIM details from database using vim_id and returns
the Connection Info to connect Juju Controller.
Args:
vim_id (str): VIM ID
Returns:
ConnectionInfo (object)
"""
vim_content = self.db.get_one("vim_accounts", {"_id": vim_id})
endpoint = vim_content["vim_url"]
username = vim_content["vim_user"]
vim_config = vim_content["config"]
cacert = vim_config["ca_cert_content"]
cloud_name = vim_config["cloud"]
cloud_credentials = vim_config["cloud_credentials"]
password = self._decrypt_password(vim_content)
return ConnectionInfo(
endpoint, username, password, cacert, cloud_name, cloud_credentials
)
def _get_libjuju(self, vim_uuid):
connection_info = self._get_connection_info(vim_uuid)
return Libjuju(connection_info)
@activity.defn(name=ACTIVITY_TEST_VIM_CONNECTIVITY)
async def test_vim_connectivity(
self, test_connectivity_input: TestVimConnectivityInput
) -> None:
"""Validates the credentials by attempting to connect to the given Juju Controller.
Collaborators:
DB Read: vim_accounts
Juju Controller: Connect only
Raises (Retryable):
ApplicationError If any of password, cacert, cloud_credentials is invalid
or Juju controller is not reachable
Activity Lifecycle:
This activity should complete relatively quickly (in a few seconds).
However, it would be reasonable to wait more than 72 seconds (network timeout)
incase there are network issues.
This activity will not report a heartbeat due to its
short-running nature.
It is recommended, although not necessary to implement a
back-off strategy for this activity, as it will naturally block
and wait on each connection attempt.
"""
vim_id = test_connectivity_input.vim_uuid
controller = None
try:
libjuju = self._get_libjuju(vim_id)
controller = await libjuju.get_controller()
message = f"Connection to juju controller succeeded for {vim_id}"
activity.logger.info(message)
finally:
await libjuju.disconnect_controller(controller)
@activity.defn(name=ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST)
async def create_model_if_doesnt_exist(
self, create_model_input: CreateModelInput
) -> None:
"""Connects to Juju Controller. Create a new model if model_name does not exist
Collaborators:
DB Read: vim_accounts
Juju Controller: Connect and create model.
Raises (Retryable):
ApplicationError If Juju controller is not reachable
Activity Lifecycle:
This activity should complete relatively quickly (in a few seconds).
However, it would be reasonable to wait more than 72 seconds (network timeout)
incase there are network issues.
This activity will not report a heartbeat due to its
short-running nature.
It is recommended, although not necessary to implement a
back-off strategy for this activity, as it will naturally block
and wait on each connection attempt.
"""
model_name = create_model_input.model_name
libjuju = self._get_libjuju(create_model_input.vim_uuid)
await libjuju.add_model(model_name)
@activity.defn(name=ACTIVITY_DEPLOY_CHARM)
async def deploy_charm(self, deploy_charm_input: VduInstantiateInput) -> None:
"""Deploys a charm.
Collaborators:
DB Read: vim_accounts
Juju Controller: Connect and deploy charm
Raises (Retryable):
ApplicationError If Juju controller is not reachable
Activity Lifecycle:
This activity should complete relatively quickly (in a few seconds).
However, it would be reasonable to wait more than 72 seconds (network timeout)
incase there are network issues.
This activity will not report a heartbeat due to its
short-running nature.
It is recommended, although not necessary to implement a
back-off strategy for this activity, as it will naturally block
and wait on each connection attempt.
"""
model_name = deploy_charm_input.model_name
libjuju = self._get_libjuju(deploy_charm_input.vim_uuid)
charm_info = deploy_charm_input.charm_info
await libjuju.deploy_charm(
application_name=charm_info.app_name,
path=charm_info.entity_url,
model_name=model_name,
channel=charm_info.channel,
)
@activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS)
async def check_charm_status(self, check_charm_status: VduInstantiateInput) -> None:
"""Validates the credentials by attempting to connect to the given Juju Controller.
Collaborators:
DB Read: vim_accounts
Juju Controller: Connect to controller and check charm status.
Raises (Retryable):
ApplicationError If any of password, cacert, cloud_credentials is invalid
or Juju controller is not reachable
Activity Lifecycle:
This activity should complete relatively quickly (in a few seconds).
However, it would be reasonable to wait more than 72 seconds (network timeout)
incase there are network issues.
This activity will not report a heartbeat due to its
short-running nature.
It is recommended, although not necessary to implement a
back-off strategy for this activity, as it will naturally block
and wait on each connection attempt.
"""
pass
class VimDbActivity:
"""Perform Database operations for VIM accounts.
Args:
db (object): Data Access Object
"""
def __init__(self, db):
self.db = db
@activity.defn(name=ACTIVITY_UPDATE_VIM_STATE)
async def update_vim_state(self, data: UpdateVimStateInput) -> None:
"""
Changes the state of the VIM itself. Should be either
ENABLED or ERROR, however this activity does not validate
the state as no validation was done in OSM previously.
Collaborators:
DB Write: vim_accounts
Raises (Retryable):
DbException If the target DB record does not exist or DB is not reachable.
Activity Lifecycle:
This activity will not report a heartbeat due to its
short-running nature.
As this is a direct DB update, it is not recommended to have
any specific retry policy
"""
update_vim_state = {
"_admin.operationalState": data.operational_state.name,
"_admin.detailed-status": data.message,
"_admin.modified": time(),
}
self.db.set_one("vim_accounts", {"_id": data.vim_uuid}, update_vim_state)
activity.logger.debug(
f"Updated VIM {data.vim_uuid} to {data.operational_state.name}"
)
@activity.defn(name=ACTIVITY_UPDATE_VIM_OPERATION_STATE)
async def update_vim_operation_state(
self, data: UpdateVimOperationStateInput
) -> None:
"""
Changes the state of a VIM operation task. Should be done to
indicate progress, or completion of the task itself.
Collaborators:
DB Write: vim_accounts
Raises (Retryable):
DbException If the target DB record does not exist or DB is not reachable.
Activity Lifecycle:
This activity will not report a heartbeat due to its
short-running nature.
As this is a direct DB update, it is not recommended to have
any specific retry policy
"""
update_operation_state = {
f"_admin.operations.{format(data.op_id)}.operationState": data.op_state.name,
f"_admin.operations.{format(data.op_id)}.detailed-status": data.message,
"_admin.current_operation": None,
}
self.db.set_one("vim_accounts", {"_id": data.vim_uuid}, update_operation_state)
activity.logger.debug(
f"Updated VIM {data.vim_uuid} OP ID {data.op_id} to {data.op_state.name}"
)
@activity.defn(name=ACTIVITY_DELETE_VIM)
async def delete_vim_record(self, data: DeleteVimInput) -> None:
"""
Deletes the VIM record from the database.
Collaborators:
DB Delete: vim_accounts
Raises (Retryable):
DbException If the target DB record does not exist or DB is not reachable.
Activity Lifecycle:
This activity will not report a heartbeat due to its
short-running nature.
As this is a direct DB update, it is not recommended to have
any specific retry policy
"""
self.db.del_one("vim_accounts", {"_id": data.vim_uuid})
activity.logger.debug(f"Removed VIM {data.vim_uuid}")