| ####################################################################################### |
| # 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}") |