| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 1 | ####################################################################################### |
| 2 | # Copyright ETSI Contributors and Others. |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| 13 | # implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | import logging |
| 18 | from temporalio import activity |
| 19 | from time import time |
| 20 | from n2vc.temporal_libjuju import ConnectionInfo, Libjuju |
| 21 | from osm_common.temporal_constants import ( |
| 22 | ACTIVITY_DELETE_VIM, |
| 23 | ACTIVITY_TEST_VIM_CONNECTIVITY, |
| 24 | ACTIVITY_UPDATE_VIM_OPERATION_STATE, |
| 25 | ACTIVITY_UPDATE_VIM_STATE, |
| Patricia Reinoso | 1fa7b6d | 2023-04-05 15:27:20 +0000 | [diff] [blame^] | 26 | ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST, |
| 27 | ACTIVITY_DEPLOY_CHARM, |
| 28 | ACTIVITY_CHECK_CHARM_STATUS, |
| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 29 | ) |
| 30 | from osm_common.dataclasses.temporal_dataclasses import ( |
| 31 | DeleteVimInput, |
| 32 | TestVimConnectivityInput, |
| 33 | UpdateVimOperationStateInput, |
| 34 | UpdateVimStateInput, |
| Patricia Reinoso | 1fa7b6d | 2023-04-05 15:27:20 +0000 | [diff] [blame^] | 35 | CreateModelInput, |
| 36 | VduInstantiateInput, |
| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 37 | ) |
| 38 | |
| 39 | activity.logger = logging.getLogger("lcm.temporal.vim_activities") |
| 40 | |
| 41 | |
| 42 | class JujuPaasConnector: |
| 43 | """Handles Juju Controller operations. |
| 44 | |
| 45 | Args: |
| 46 | db (object): Data Access Object |
| 47 | """ |
| 48 | |
| 49 | def __init__(self, db): |
| 50 | self.db = db |
| 51 | |
| 52 | def _decrypt_password(self, vim_content: dict) -> str: |
| 53 | """Decrypt a password. |
| 54 | vim_content (dict): VIM details as a dictionary |
| 55 | |
| 56 | Returns: |
| 57 | plain text password (str) |
| 58 | """ |
| 59 | return self.db.decrypt( |
| 60 | vim_content["vim_password"], |
| 61 | schema_version=vim_content["schema_version"], |
| 62 | salt=vim_content["_id"], |
| 63 | ) |
| 64 | |
| 65 | def _get_connection_info(self, vim_id: str) -> ConnectionInfo: |
| 66 | """Get VIM details from database using vim_id and returns |
| 67 | the Connection Info to connect Juju Controller. |
| 68 | |
| 69 | Args: |
| 70 | vim_id (str): VIM ID |
| 71 | |
| 72 | Returns: |
| 73 | ConnectionInfo (object) |
| 74 | """ |
| 75 | vim_content = self.db.get_one("vim_accounts", {"_id": vim_id}) |
| 76 | endpoint = vim_content["vim_url"] |
| 77 | username = vim_content["vim_user"] |
| 78 | vim_config = vim_content["config"] |
| 79 | cacert = vim_config["ca_cert_content"] |
| 80 | cloud_name = vim_config["cloud"] |
| 81 | cloud_credentials = vim_config["cloud_credentials"] |
| 82 | password = self._decrypt_password(vim_content) |
| 83 | return ConnectionInfo( |
| 84 | endpoint, username, password, cacert, cloud_name, cloud_credentials |
| 85 | ) |
| 86 | |
| Patricia Reinoso | 1fa7b6d | 2023-04-05 15:27:20 +0000 | [diff] [blame^] | 87 | def _get_libjuju(self, vim_uuid): |
| 88 | connection_info = self._get_connection_info(vim_uuid) |
| 89 | return Libjuju(connection_info) |
| 90 | |
| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 91 | @activity.defn(name=ACTIVITY_TEST_VIM_CONNECTIVITY) |
| 92 | async def test_vim_connectivity( |
| 93 | self, test_connectivity_input: TestVimConnectivityInput |
| 94 | ) -> None: |
| 95 | """Validates the credentials by attempting to connect to the given Juju Controller. |
| 96 | |
| 97 | Collaborators: |
| 98 | DB Read: vim_accounts |
| 99 | Juju Controller: Connect only |
| 100 | |
| 101 | Raises (Retryable): |
| 102 | ApplicationError If any of password, cacert, cloud_credentials is invalid |
| 103 | or Juju controller is not reachable |
| 104 | |
| 105 | Activity Lifecycle: |
| 106 | This activity should complete relatively quickly (in a few seconds). |
| 107 | However, it would be reasonable to wait more than 72 seconds (network timeout) |
| 108 | incase there are network issues. |
| 109 | |
| 110 | This activity will not report a heartbeat due to its |
| 111 | short-running nature. |
| 112 | |
| 113 | It is recommended, although not necessary to implement a |
| 114 | back-off strategy for this activity, as it will naturally block |
| 115 | and wait on each connection attempt. |
| 116 | """ |
| 117 | vim_id = test_connectivity_input.vim_uuid |
| 118 | controller = None |
| 119 | try: |
| Patricia Reinoso | 1fa7b6d | 2023-04-05 15:27:20 +0000 | [diff] [blame^] | 120 | libjuju = self._get_libjuju(vim_id) |
| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 121 | controller = await libjuju.get_controller() |
| 122 | message = f"Connection to juju controller succeeded for {vim_id}" |
| 123 | activity.logger.info(message) |
| 124 | finally: |
| 125 | await libjuju.disconnect_controller(controller) |
| 126 | |
| Patricia Reinoso | 1fa7b6d | 2023-04-05 15:27:20 +0000 | [diff] [blame^] | 127 | @activity.defn(name=ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST) |
| 128 | async def create_model_if_doesnt_exist( |
| 129 | self, create_model_input: CreateModelInput |
| 130 | ) -> None: |
| 131 | """Connects to Juju Controller. Create a new model if model_name does not exist |
| 132 | |
| 133 | Collaborators: |
| 134 | DB Read: vim_accounts |
| 135 | Juju Controller: Connect and create model. |
| 136 | |
| 137 | Raises (Retryable): |
| 138 | ApplicationError If Juju controller is not reachable |
| 139 | |
| 140 | Activity Lifecycle: |
| 141 | This activity should complete relatively quickly (in a few seconds). |
| 142 | However, it would be reasonable to wait more than 72 seconds (network timeout) |
| 143 | incase there are network issues. |
| 144 | |
| 145 | This activity will not report a heartbeat due to its |
| 146 | short-running nature. |
| 147 | |
| 148 | It is recommended, although not necessary to implement a |
| 149 | back-off strategy for this activity, as it will naturally block |
| 150 | and wait on each connection attempt. |
| 151 | """ |
| 152 | model_name = create_model_input.model_name |
| 153 | libjuju = self._get_libjuju(create_model_input.vim_uuid) |
| 154 | await libjuju.add_model(model_name) |
| 155 | |
| 156 | @activity.defn(name=ACTIVITY_DEPLOY_CHARM) |
| 157 | async def deploy_charm(self, deploy_charm_input: VduInstantiateInput) -> None: |
| 158 | """Deploys a charm. |
| 159 | |
| 160 | Collaborators: |
| 161 | DB Read: vim_accounts |
| 162 | Juju Controller: Connect and deploy charm |
| 163 | |
| 164 | Raises (Retryable): |
| 165 | ApplicationError If Juju controller is not reachable |
| 166 | |
| 167 | Activity Lifecycle: |
| 168 | This activity should complete relatively quickly (in a few seconds). |
| 169 | However, it would be reasonable to wait more than 72 seconds (network timeout) |
| 170 | incase there are network issues. |
| 171 | |
| 172 | This activity will not report a heartbeat due to its |
| 173 | short-running nature. |
| 174 | |
| 175 | It is recommended, although not necessary to implement a |
| 176 | back-off strategy for this activity, as it will naturally block |
| 177 | and wait on each connection attempt. |
| 178 | """ |
| 179 | model_name = deploy_charm_input.model_name |
| 180 | libjuju = self._get_libjuju(deploy_charm_input.vim_uuid) |
| 181 | charm_info = deploy_charm_input.charm_info |
| 182 | await libjuju.deploy_charm( |
| 183 | application_name=charm_info.app_name, |
| 184 | path=charm_info.entity_url, |
| 185 | model_name=model_name, |
| 186 | channel=charm_info.channel, |
| 187 | ) |
| 188 | |
| 189 | @activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS) |
| 190 | async def check_charm_status(self, check_charm_status: VduInstantiateInput) -> None: |
| 191 | """Validates the credentials by attempting to connect to the given Juju Controller. |
| 192 | |
| 193 | Collaborators: |
| 194 | DB Read: vim_accounts |
| 195 | Juju Controller: Connect to controller and check charm status. |
| 196 | |
| 197 | Raises (Retryable): |
| 198 | ApplicationError If any of password, cacert, cloud_credentials is invalid |
| 199 | or Juju controller is not reachable |
| 200 | |
| 201 | Activity Lifecycle: |
| 202 | This activity should complete relatively quickly (in a few seconds). |
| 203 | However, it would be reasonable to wait more than 72 seconds (network timeout) |
| 204 | incase there are network issues. |
| 205 | |
| 206 | This activity will not report a heartbeat due to its |
| 207 | short-running nature. |
| 208 | |
| 209 | It is recommended, although not necessary to implement a |
| 210 | back-off strategy for this activity, as it will naturally block |
| 211 | and wait on each connection attempt. |
| 212 | """ |
| 213 | pass |
| 214 | |
| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 215 | |
| 216 | class VimDbActivity: |
| 217 | """Perform Database operations for VIM accounts. |
| 218 | |
| 219 | Args: |
| 220 | db (object): Data Access Object |
| 221 | """ |
| 222 | |
| 223 | def __init__(self, db): |
| 224 | self.db = db |
| 225 | |
| 226 | @activity.defn(name=ACTIVITY_UPDATE_VIM_STATE) |
| 227 | async def update_vim_state(self, data: UpdateVimStateInput) -> None: |
| 228 | """ |
| 229 | Changes the state of the VIM itself. Should be either |
| 230 | ENABLED or ERROR, however this activity does not validate |
| 231 | the state as no validation was done in OSM previously. |
| 232 | |
| 233 | Collaborators: |
| 234 | DB Write: vim_accounts |
| 235 | |
| 236 | Raises (Retryable): |
| 237 | DbException If the target DB record does not exist or DB is not reachable. |
| 238 | |
| 239 | Activity Lifecycle: |
| 240 | This activity will not report a heartbeat due to its |
| 241 | short-running nature. |
| 242 | |
| 243 | As this is a direct DB update, it is not recommended to have |
| 244 | any specific retry policy |
| 245 | """ |
| 246 | update_vim_state = { |
| 247 | "_admin.operationalState": data.operational_state.name, |
| 248 | "_admin.detailed-status": data.message, |
| 249 | "_admin.modified": time(), |
| 250 | } |
| 251 | |
| 252 | self.db.set_one("vim_accounts", {"_id": data.vim_uuid}, update_vim_state) |
| 253 | activity.logger.debug( |
| 254 | f"Updated VIM {data.vim_uuid} to {data.operational_state.name}" |
| 255 | ) |
| 256 | |
| 257 | @activity.defn(name=ACTIVITY_UPDATE_VIM_OPERATION_STATE) |
| 258 | async def update_vim_operation_state( |
| 259 | self, data: UpdateVimOperationStateInput |
| 260 | ) -> None: |
| 261 | """ |
| 262 | Changes the state of a VIM operation task. Should be done to |
| 263 | indicate progress, or completion of the task itself. |
| 264 | |
| 265 | Collaborators: |
| 266 | DB Write: vim_accounts |
| 267 | |
| 268 | Raises (Retryable): |
| 269 | DbException If the target DB record does not exist or DB is not reachable. |
| 270 | |
| 271 | Activity Lifecycle: |
| 272 | This activity will not report a heartbeat due to its |
| 273 | short-running nature. |
| 274 | |
| 275 | As this is a direct DB update, it is not recommended to have |
| 276 | any specific retry policy |
| 277 | """ |
| 278 | update_operation_state = { |
| 279 | f"_admin.operations.{format(data.op_id)}.operationState": data.op_state.name, |
| 280 | f"_admin.operations.{format(data.op_id)}.detailed-status": data.message, |
| 281 | "_admin.current_operation": None, |
| 282 | } |
| 283 | |
| 284 | self.db.set_one("vim_accounts", {"_id": data.vim_uuid}, update_operation_state) |
| 285 | activity.logger.debug( |
| 286 | f"Updated VIM {data.vim_uuid} OP ID {data.op_id} to {data.op_state.name}" |
| 287 | ) |
| 288 | |
| 289 | @activity.defn(name=ACTIVITY_DELETE_VIM) |
| 290 | async def delete_vim_record(self, data: DeleteVimInput) -> None: |
| 291 | """ |
| 292 | Deletes the VIM record from the database. |
| 293 | |
| 294 | Collaborators: |
| 295 | DB Delete: vim_accounts |
| 296 | |
| Mark Beierl | 2bed607 | 2023-04-05 20:01:41 +0000 | [diff] [blame] | 297 | Raises (Retryable): |
| Patricia Reinoso | 02a39fd | 2023-03-08 17:13:56 +0000 | [diff] [blame] | 298 | DbException If the target DB record does not exist or DB is not reachable. |
| 299 | |
| 300 | Activity Lifecycle: |
| 301 | This activity will not report a heartbeat due to its |
| 302 | short-running nature. |
| 303 | |
| 304 | As this is a direct DB update, it is not recommended to have |
| 305 | any specific retry policy |
| 306 | """ |
| 307 | |
| 308 | self.db.del_one("vim_accounts", {"_id": data.vim_uuid}) |
| 309 | activity.logger.debug(f"Removed VIM {data.vim_uuid}") |