blob: c84713aa5a3bd6ae362f6997a571f56a2e10bc86 [file] [log] [blame]
Patricia Reinoso02a39fd2023-03-08 17:13:56 +00001#######################################################################################
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
17import logging
18from temporalio import activity
19from time import time
20from n2vc.temporal_libjuju import ConnectionInfo, Libjuju
21from 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 Reinoso1fa7b6d2023-04-05 15:27:20 +000026 ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST,
27 ACTIVITY_DEPLOY_CHARM,
28 ACTIVITY_CHECK_CHARM_STATUS,
Patricia Reinoso02a39fd2023-03-08 17:13:56 +000029)
30from osm_common.dataclasses.temporal_dataclasses import (
31 DeleteVimInput,
32 TestVimConnectivityInput,
33 UpdateVimOperationStateInput,
34 UpdateVimStateInput,
Patricia Reinoso1fa7b6d2023-04-05 15:27:20 +000035 CreateModelInput,
36 VduInstantiateInput,
Patricia Reinoso02a39fd2023-03-08 17:13:56 +000037)
38
39activity.logger = logging.getLogger("lcm.temporal.vim_activities")
40
41
42class 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 Reinoso1fa7b6d2023-04-05 15:27:20 +000087 def _get_libjuju(self, vim_uuid):
88 connection_info = self._get_connection_info(vim_uuid)
89 return Libjuju(connection_info)
90
Patricia Reinoso02a39fd2023-03-08 17:13:56 +000091 @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 Reinoso1fa7b6d2023-04-05 15:27:20 +0000120 libjuju = self._get_libjuju(vim_id)
Patricia Reinoso02a39fd2023-03-08 17:13:56 +0000121 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 Reinoso1fa7b6d2023-04-05 15:27:20 +0000127 @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 Reinoso02a39fd2023-03-08 17:13:56 +0000215
216class 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 Beierl2bed6072023-04-05 20:01:41 +0000297 Raises (Retryable):
Patricia Reinoso02a39fd2023-03-08 17:13:56 +0000298 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}")