blob: f28a9bd02fe7e68a97ced658dc3dd4fa70672bc5 [file] [log] [blame]
quilesj29114342019-10-29 09:30:44 +01001##
2# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3# This file is part of OSM
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15# implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# For those usages not covered by the Apache License, Version 2.0 please
20# contact with: nfvlabs@tid.es
21##
22
quilesj29114342019-10-29 09:30:44 +010023import asyncio
beierlmf52cb7c2020-04-21 16:36:35 -040024import logging
quilesj29114342019-10-29 09:30:44 +010025
David Garciaeb8943a2021-04-12 12:07:37 +020026from n2vc.config import EnvironConfig
David Garcia582b9232021-10-26 12:30:44 +020027from n2vc.definitions import RelationEndpoint
beierlmf52cb7c2020-04-21 16:36:35 -040028from n2vc.exceptions import (
29 N2VCBadArgumentsException,
30 N2VCException,
31 N2VCConnectionException,
32 N2VCExecutionException,
aktasfa02f8a2021-07-29 17:41:40 +030033 N2VCApplicationExists,
34 JujuApplicationExists,
almagiaba6e5322020-09-16 09:44:40 +020035 # N2VCNotFound,
beierlmf52cb7c2020-04-21 16:36:35 -040036 MethodNotImplemented,
37)
quilesj29114342019-10-29 09:30:44 +010038from n2vc.n2vc_conn import N2VCConnector
quilesj776ab392019-12-12 16:10:54 +000039from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
Mark Beierlfb797862023-05-18 22:21:06 -040040from n2vc.libjuju import Libjuju, retry_callback
David Garciaeb8943a2021-04-12 12:07:37 +020041from n2vc.store import MotorStore
Pedro Escaleira0ebadd82022-03-21 17:54:45 +000042from n2vc.utils import get_ee_id_components, generate_random_alfanum_string
David Garciaeb8943a2021-04-12 12:07:37 +020043from n2vc.vca.connection import get_connection
aktasfa02f8a2021-07-29 17:41:40 +030044from retrying_async import retry
aticig01244642022-07-28 01:12:03 +030045from typing import Tuple
quilesj29114342019-10-29 09:30:44 +010046
47
48class N2VCJujuConnector(N2VCConnector):
49
50 """
David Garciaeb8943a2021-04-12 12:07:37 +020051 ####################################################################################
52 ################################### P U B L I C ####################################
53 ####################################################################################
quilesj29114342019-10-29 09:30:44 +010054 """
55
David Garcia347aae62020-04-29 12:34:23 +020056 BUILT_IN_CLOUDS = ["localhost", "microk8s"]
David Garciaeb8943a2021-04-12 12:07:37 +020057 libjuju = None
David Garcia347aae62020-04-29 12:34:23 +020058
quilesj29114342019-10-29 09:30:44 +010059 def __init__(
60 self,
61 db: object,
62 fs: object,
63 log: object = None,
beierlmf52cb7c2020-04-21 16:36:35 -040064 on_update_db=None,
quilesj29114342019-10-29 09:30:44 +010065 ):
David Garciaeb8943a2021-04-12 12:07:37 +020066 """
67 Constructor
68
69 :param: db: Database object from osm_common
70 :param: fs: Filesystem object from osm_common
71 :param: log: Logger
David Garciaeb8943a2021-04-12 12:07:37 +020072 :param: on_update_db: Callback function to be called for updating the database.
quilesj29114342019-10-29 09:30:44 +010073 """
74
75 # parent class constructor
Mark Beierl2c3c1462023-05-15 16:17:02 -040076 N2VCConnector.__init__(self, db=db, fs=fs, log=log, on_update_db=on_update_db)
quilesj29114342019-10-29 09:30:44 +010077
78 # silence websocket traffic log
beierlmf52cb7c2020-04-21 16:36:35 -040079 logging.getLogger("websockets.protocol").setLevel(logging.INFO)
80 logging.getLogger("juju.client.connection").setLevel(logging.WARN)
81 logging.getLogger("model").setLevel(logging.WARN)
quilesj29114342019-10-29 09:30:44 +010082
beierlmf52cb7c2020-04-21 16:36:35 -040083 self.log.info("Initializing N2VC juju connector...")
quilesj29114342019-10-29 09:30:44 +010084
David Garciaeb8943a2021-04-12 12:07:37 +020085 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
86 self._store = MotorStore(db_uri)
Guillermo Calvino474fd952023-04-28 11:51:43 +020087 self.loading_libjuju = asyncio.Lock()
David Garcia1423ffa2022-05-04 15:33:03 +020088 self.delete_namespace_locks = {}
beierlmf52cb7c2020-04-21 16:36:35 -040089 self.log.info("N2VC juju connector initialized")
quilesj29114342019-10-29 09:30:44 +010090
David Garciaeb8943a2021-04-12 12:07:37 +020091 async def get_status(
92 self, namespace: str, yaml_format: bool = True, vca_id: str = None
93 ):
94 """
95 Get status from all juju models from a VCA
96
97 :param namespace: we obtain ns from namespace
98 :param yaml_format: returns a yaml string
99 :param: vca_id: VCA ID from which the status will be retrieved.
100 """
101 # TODO: Review where is this function used. It is not optimal at all to get the status
102 # from all the juju models of a particular VCA. Additionally, these models might
103 # not have been deployed by OSM, in that case we are getting information from
104 # deployments outside of OSM's scope.
quilesj776ab392019-12-12 16:10:54 +0000105
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100106 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200107 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100108
beierlmf52cb7c2020-04-21 16:36:35 -0400109 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
110 namespace=namespace
111 )
quilesj29114342019-10-29 09:30:44 +0100112 # model name is ns_id
113 model_name = ns_id
114 if model_name is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400115 msg = "Namespace {} not valid".format(namespace)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100116 self.log.error(msg)
beierlmf52cb7c2020-04-21 16:36:35 -0400117 raise N2VCBadArgumentsException(msg, ["namespace"])
quilesj29114342019-10-29 09:30:44 +0100118
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200119 status = {}
David Garciaeb8943a2021-04-12 12:07:37 +0200120 models = await libjuju.list_models(contains=ns_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200121
122 for m in models:
David Garciaeb8943a2021-04-12 12:07:37 +0200123 status[m] = await libjuju.get_model_status(m)
124
quilesj776ab392019-12-12 16:10:54 +0000125 if yaml_format:
126 return obj_to_yaml(status)
127 else:
128 return obj_to_dict(status)
quilesj29114342019-10-29 09:30:44 +0100129
David Garciaeb8943a2021-04-12 12:07:37 +0200130 async def update_vca_status(self, vcastatus: dict, vca_id: str = None):
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530131 """
132 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
David Garciaeb8943a2021-04-12 12:07:37 +0200133
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530134 :param vcastatus: dict containing vcaStatus
David Garciaeb8943a2021-04-12 12:07:37 +0200135 :param: vca_id: VCA ID
136
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530137 :return: None
138 """
139 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200140 libjuju = await self._get_libjuju(vca_id)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530141 for model_name in vcastatus:
142 # Adding executed actions
garciadeblas82b591c2021-03-24 09:22:13 +0100143 vcastatus[model_name][
144 "executedActions"
145 ] = await libjuju.get_executed_actions(model_name)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530146 for application in vcastatus[model_name]["applications"]:
147 # Adding application actions
garciadeblas82b591c2021-03-24 09:22:13 +0100148 vcastatus[model_name]["applications"][application][
149 "actions"
150 ] = await libjuju.get_actions(application, model_name)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530151 # Adding application configs
garciadeblas82b591c2021-03-24 09:22:13 +0100152 vcastatus[model_name]["applications"][application][
153 "configs"
154 ] = await libjuju.get_application_configs(model_name, application)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530155 except Exception as e:
156 self.log.debug("Error in updating vca status: {}".format(str(e)))
157
quilesj29114342019-10-29 09:30:44 +0100158 async def create_execution_environment(
159 self,
160 namespace: str,
161 db_dict: dict,
162 reuse_ee_id: str = None,
163 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400164 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200165 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100166 ) -> (str, dict):
David Garciaeb8943a2021-04-12 12:07:37 +0200167 """
168 Create an Execution Environment. Returns when it is created or raises an
169 exception on failing
170
171 :param: namespace: Contains a dot separate string.
172 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
173 :param: db_dict: where to write to database when the status changes.
174 It contains a dictionary with {collection: str, filter: {}, path: str},
175 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
176 "_admin.deployed.VCA.3"}
177 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
178 older environment
179 :param: progress_timeout: Progress timeout
180 :param: total_timeout: Total timeout
181 :param: vca_id: VCA ID
182
183 :returns: id of the new execution environment and credentials for it
184 (credentials can contains hostname, username, etc depending on underlying cloud)
185 """
quilesj29114342019-10-29 09:30:44 +0100186
beierlmf52cb7c2020-04-21 16:36:35 -0400187 self.log.info(
188 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
189 namespace, reuse_ee_id
190 )
191 )
David Garciaeb8943a2021-04-12 12:07:37 +0200192 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100193
quilesj29114342019-10-29 09:30:44 +0100194 machine_id = None
195 if reuse_ee_id:
beierlmf52cb7c2020-04-21 16:36:35 -0400196 model_name, application_name, machine_id = self._get_ee_id_components(
197 ee_id=reuse_ee_id
198 )
quilesj29114342019-10-29 09:30:44 +0100199 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400200 (
201 _nsi_id,
202 ns_id,
203 _vnf_id,
204 _vdu_id,
205 _vdu_count,
206 ) = self._get_namespace_components(namespace=namespace)
quilesj29114342019-10-29 09:30:44 +0100207 # model name is ns_id
208 model_name = ns_id
209 # application name
210 application_name = self._get_application_name(namespace=namespace)
211
beierlmf52cb7c2020-04-21 16:36:35 -0400212 self.log.debug(
213 "model name: {}, application name: {}, machine_id: {}".format(
214 model_name, application_name, machine_id
215 )
216 )
quilesj29114342019-10-29 09:30:44 +0100217
218 # create or reuse a new juju machine
219 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200220 if not await libjuju.model_exists(model_name):
Patricia Reinoso085942e2022-12-05 16:55:51 +0000221 await libjuju.add_model(model_name, libjuju.vca_connection.lxd_cloud)
David Garciaeb8943a2021-04-12 12:07:37 +0200222 machine, new = await libjuju.create_machine(
quilesj29114342019-10-29 09:30:44 +0100223 model_name=model_name,
quilesj29114342019-10-29 09:30:44 +0100224 machine_id=machine_id,
225 db_dict=db_dict,
226 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400227 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100228 )
David Garcia4fee80e2020-05-13 12:18:38 +0200229 # id for the execution environment
230 ee_id = N2VCJujuConnector._build_ee_id(
231 model_name=model_name,
232 application_name=application_name,
233 machine_id=str(machine.entity_id),
234 )
235 self.log.debug("ee_id: {}".format(ee_id))
236
237 if new:
238 # write ee_id in database
239 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
240
quilesj29114342019-10-29 09:30:44 +0100241 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400242 message = "Error creating machine on juju: {}".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100243 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100244 raise N2VCException(message=message)
245
quilesj29114342019-10-29 09:30:44 +0100246 # new machine credentials
Patricia Reinoso085942e2022-12-05 16:55:51 +0000247 credentials = {"hostname": machine.dns_name}
quilesj29114342019-10-29 09:30:44 +0100248
beierlmf52cb7c2020-04-21 16:36:35 -0400249 self.log.info(
250 "Execution environment created. ee_id: {}, credentials: {}".format(
251 ee_id, credentials
252 )
253 )
quilesj29114342019-10-29 09:30:44 +0100254
255 return ee_id, credentials
256
257 async def register_execution_environment(
258 self,
259 namespace: str,
260 credentials: dict,
261 db_dict: dict,
262 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400263 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200264 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100265 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200266 """
267 Register an existing execution environment at the VCA
quilesj29114342019-10-29 09:30:44 +0100268
David Garciaeb8943a2021-04-12 12:07:37 +0200269 :param: namespace: Contains a dot separate string.
270 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
271 :param: credentials: credentials to access the existing execution environment
272 (it can contains hostname, username, path to private key,
273 etc depending on underlying cloud)
274 :param: db_dict: where to write to database when the status changes.
275 It contains a dictionary with {collection: str, filter: {}, path: str},
276 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
277 "_admin.deployed.VCA.3"}
278 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
279 older environment
280 :param: progress_timeout: Progress timeout
281 :param: total_timeout: Total timeout
282 :param: vca_id: VCA ID
283
284 :returns: id of the execution environment
285 """
beierlmf52cb7c2020-04-21 16:36:35 -0400286 self.log.info(
287 "Registering execution environment. namespace={}, credentials={}".format(
288 namespace, credentials
289 )
290 )
David Garciaeb8943a2021-04-12 12:07:37 +0200291 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100292
293 if credentials is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400294 raise N2VCBadArgumentsException(
295 message="credentials are mandatory", bad_args=["credentials"]
296 )
297 if credentials.get("hostname"):
298 hostname = credentials["hostname"]
quilesj29114342019-10-29 09:30:44 +0100299 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400300 raise N2VCBadArgumentsException(
301 message="hostname is mandatory", bad_args=["credentials.hostname"]
302 )
303 if credentials.get("username"):
304 username = credentials["username"]
quilesj29114342019-10-29 09:30:44 +0100305 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400306 raise N2VCBadArgumentsException(
307 message="username is mandatory", bad_args=["credentials.username"]
308 )
309 if "private_key_path" in credentials:
310 private_key_path = credentials["private_key_path"]
quilesj29114342019-10-29 09:30:44 +0100311 else:
312 # if not passed as argument, use generated private key path
313 private_key_path = self.private_key_path
314
beierlmf52cb7c2020-04-21 16:36:35 -0400315 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
316 namespace=namespace
317 )
quilesj29114342019-10-29 09:30:44 +0100318
319 # model name
320 model_name = ns_id
321 # application name
322 application_name = self._get_application_name(namespace=namespace)
323
324 # register machine on juju
325 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200326 if not await libjuju.model_exists(model_name):
Patricia Reinoso085942e2022-12-05 16:55:51 +0000327 await libjuju.add_model(model_name, libjuju.vca_connection.lxd_cloud)
David Garciaeb8943a2021-04-12 12:07:37 +0200328 machine_id = await libjuju.provision_machine(
quilesj29114342019-10-29 09:30:44 +0100329 model_name=model_name,
330 hostname=hostname,
331 username=username,
332 private_key_path=private_key_path,
333 db_dict=db_dict,
334 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400335 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100336 )
337 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400338 self.log.error("Error registering machine: {}".format(e))
339 raise N2VCException(
340 message="Error registering machine on juju: {}".format(e)
341 )
quilesjac4e0de2019-11-27 16:12:02 +0000342
beierlmf52cb7c2020-04-21 16:36:35 -0400343 self.log.info("Machine registered: {}".format(machine_id))
quilesj29114342019-10-29 09:30:44 +0100344
345 # id for the execution environment
346 ee_id = N2VCJujuConnector._build_ee_id(
347 model_name=model_name,
348 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400349 machine_id=str(machine_id),
quilesj29114342019-10-29 09:30:44 +0100350 )
351
beierlmf52cb7c2020-04-21 16:36:35 -0400352 self.log.info("Execution environment registered. ee_id: {}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100353
354 return ee_id
355
aktasfa02f8a2021-07-29 17:41:40 +0300356 # In case of native_charm is being deployed, if JujuApplicationExists error happens
357 # it will try to add_unit
Mark Beierlfb797862023-05-18 22:21:06 -0400358 @retry(
359 attempts=3,
360 delay=5,
361 retry_exceptions=(N2VCApplicationExists,),
362 timeout=None,
363 callback=retry_callback,
364 )
quilesj29114342019-10-29 09:30:44 +0100365 async def install_configuration_sw(
366 self,
367 ee_id: str,
368 artifact_path: str,
369 db_dict: dict,
370 progress_timeout: float = None,
David Garciadfaa6e82020-04-01 16:06:39 +0200371 total_timeout: float = None,
372 config: dict = None,
David Garciaf8a9d462020-03-25 18:19:02 +0100373 num_units: int = 1,
David Garciaeb8943a2021-04-12 12:07:37 +0200374 vca_id: str = None,
aktasfa02f8a2021-07-29 17:41:40 +0300375 scaling_out: bool = False,
376 vca_type: str = None,
quilesj29114342019-10-29 09:30:44 +0100377 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200378 """
379 Install the software inside the execution environment identified by ee_id
380
381 :param: ee_id: the id of the execution environment returned by
382 create_execution_environment or register_execution_environment
383 :param: artifact_path: where to locate the artifacts (parent folder) using
384 the self.fs
385 the final artifact path will be a combination of this
386 artifact_path and additional string from the config_dict
387 (e.g. charm name)
388 :param: db_dict: where to write into database when the status changes.
389 It contains a dict with
390 {collection: <str>, filter: {}, path: <str>},
391 e.g. {collection: "nsrs", filter:
392 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
393 :param: progress_timeout: Progress timeout
394 :param: total_timeout: Total timeout
395 :param: config: Dictionary with deployment config information.
396 :param: num_units: Number of units to deploy of a particular charm.
397 :param: vca_id: VCA ID
aktasfa02f8a2021-07-29 17:41:40 +0300398 :param: scaling_out: Boolean to indicate if it is a scaling out operation
399 :param: vca_type: VCA type
David Garciaeb8943a2021-04-12 12:07:37 +0200400 """
quilesj29114342019-10-29 09:30:44 +0100401
beierlmf52cb7c2020-04-21 16:36:35 -0400402 self.log.info(
403 (
404 "Installing configuration sw on ee_id: {}, "
405 "artifact path: {}, db_dict: {}"
406 ).format(ee_id, artifact_path, db_dict)
407 )
David Garciaeb8943a2021-04-12 12:07:37 +0200408 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100409
quilesj29114342019-10-29 09:30:44 +0100410 # check arguments
411 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400412 raise N2VCBadArgumentsException(
413 message="ee_id is mandatory", bad_args=["ee_id"]
414 )
quilesj29114342019-10-29 09:30:44 +0100415 if artifact_path is None or len(artifact_path) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400416 raise N2VCBadArgumentsException(
417 message="artifact_path is mandatory", bad_args=["artifact_path"]
418 )
quilesj29114342019-10-29 09:30:44 +0100419 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400420 raise N2VCBadArgumentsException(
421 message="db_dict is mandatory", bad_args=["db_dict"]
422 )
quilesj29114342019-10-29 09:30:44 +0100423
424 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400425 (
426 model_name,
427 application_name,
428 machine_id,
429 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
430 self.log.debug(
431 "model: {}, application: {}, machine: {}".format(
432 model_name, application_name, machine_id
433 )
434 )
435 except Exception:
quilesj29114342019-10-29 09:30:44 +0100436 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400437 message="ee_id={} is not a valid execution environment id".format(
438 ee_id
439 ),
440 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100441 )
442
443 # remove // in charm path
beierlmf52cb7c2020-04-21 16:36:35 -0400444 while artifact_path.find("//") >= 0:
445 artifact_path = artifact_path.replace("//", "/")
quilesj29114342019-10-29 09:30:44 +0100446
447 # check charm path
David Garcia7114f652021-10-26 17:24:21 +0200448 if not self.fs.file_exists(artifact_path):
beierlmf52cb7c2020-04-21 16:36:35 -0400449 msg = "artifact path does not exist: {}".format(artifact_path)
450 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
quilesj29114342019-10-29 09:30:44 +0100451
beierlmf52cb7c2020-04-21 16:36:35 -0400452 if artifact_path.startswith("/"):
quilesj29114342019-10-29 09:30:44 +0100453 full_path = self.fs.path + artifact_path
454 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400455 full_path = self.fs.path + "/" + artifact_path
quilesj29114342019-10-29 09:30:44 +0100456
457 try:
aktasfa02f8a2021-07-29 17:41:40 +0300458 if vca_type == "native_charm" and await libjuju.check_application_exists(
459 model_name, application_name
460 ):
461 await libjuju.add_unit(
462 application_name=application_name,
463 model_name=model_name,
464 machine_id=machine_id,
465 db_dict=db_dict,
466 progress_timeout=progress_timeout,
467 total_timeout=total_timeout,
468 )
469 else:
470 await libjuju.deploy_charm(
471 model_name=model_name,
472 application_name=application_name,
473 path=full_path,
474 machine_id=machine_id,
475 db_dict=db_dict,
476 progress_timeout=progress_timeout,
477 total_timeout=total_timeout,
478 config=config,
479 num_units=num_units,
480 )
481 except JujuApplicationExists as e:
482 raise N2VCApplicationExists(
483 message="Error deploying charm into ee={} : {}".format(ee_id, e.message)
quilesj29114342019-10-29 09:30:44 +0100484 )
485 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400486 raise N2VCException(
aktasfa02f8a2021-07-29 17:41:40 +0300487 message="Error deploying charm into ee={} : {}".format(ee_id, e)
beierlmf52cb7c2020-04-21 16:36:35 -0400488 )
quilesj29114342019-10-29 09:30:44 +0100489
beierlmf52cb7c2020-04-21 16:36:35 -0400490 self.log.info("Configuration sw installed")
quilesj29114342019-10-29 09:30:44 +0100491
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200492 async def install_k8s_proxy_charm(
493 self,
494 charm_name: str,
495 namespace: str,
496 artifact_path: str,
497 db_dict: dict,
498 progress_timeout: float = None,
499 total_timeout: float = None,
500 config: dict = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200501 vca_id: str = None,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200502 ) -> str:
503 """
504 Install a k8s proxy charm
505
506 :param charm_name: Name of the charm being deployed
507 :param namespace: collection of all the uuids related to the charm.
508 :param str artifact_path: where to locate the artifacts (parent folder) using
509 the self.fs
510 the final artifact path will be a combination of this artifact_path and
511 additional string from the config_dict (e.g. charm name)
512 :param dict db_dict: where to write into database when the status changes.
513 It contains a dict with
514 {collection: <str>, filter: {}, path: <str>},
515 e.g. {collection: "nsrs", filter:
516 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
David Garciaeb8943a2021-04-12 12:07:37 +0200517 :param: progress_timeout: Progress timeout
518 :param: total_timeout: Total timeout
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200519 :param config: Dictionary with additional configuration
David Garciaeb8943a2021-04-12 12:07:37 +0200520 :param vca_id: VCA ID
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200521
522 :returns ee_id: execution environment id.
523 """
David Garciaeb8943a2021-04-12 12:07:37 +0200524 self.log.info(
525 "Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}".format(
526 charm_name, artifact_path, db_dict
527 )
528 )
529 libjuju = await self._get_libjuju(vca_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200530
531 if artifact_path is None or len(artifact_path) == 0:
532 raise N2VCBadArgumentsException(
533 message="artifact_path is mandatory", bad_args=["artifact_path"]
534 )
535 if db_dict is None:
David Garciaeb8943a2021-04-12 12:07:37 +0200536 raise N2VCBadArgumentsException(
537 message="db_dict is mandatory", bad_args=["db_dict"]
538 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200539
540 # remove // in charm path
David Garciaeb8943a2021-04-12 12:07:37 +0200541 while artifact_path.find("//") >= 0:
542 artifact_path = artifact_path.replace("//", "/")
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200543
544 # check charm path
David Garcia7114f652021-10-26 17:24:21 +0200545 if not self.fs.file_exists(artifact_path):
David Garciaeb8943a2021-04-12 12:07:37 +0200546 msg = "artifact path does not exist: {}".format(artifact_path)
547 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200548
David Garciaeb8943a2021-04-12 12:07:37 +0200549 if artifact_path.startswith("/"):
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200550 full_path = self.fs.path + artifact_path
551 else:
David Garciaeb8943a2021-04-12 12:07:37 +0200552 full_path = self.fs.path + "/" + artifact_path
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200553
554 _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace)
David Garciaeb8943a2021-04-12 12:07:37 +0200555 model_name = "{}-k8s".format(ns_id)
556 if not await libjuju.model_exists(model_name):
Patricia Reinoso085942e2022-12-05 16:55:51 +0000557 await libjuju.add_model(model_name, libjuju.vca_connection.k8s_cloud)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200558 application_name = self._get_application_name(namespace)
559
560 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200561 await libjuju.deploy_charm(
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200562 model_name=model_name,
563 application_name=application_name,
564 path=full_path,
565 machine_id=None,
566 db_dict=db_dict,
567 progress_timeout=progress_timeout,
568 total_timeout=total_timeout,
David Garciaeb8943a2021-04-12 12:07:37 +0200569 config=config,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200570 )
571 except Exception as e:
David Garciaeb8943a2021-04-12 12:07:37 +0200572 raise N2VCException(message="Error deploying charm: {}".format(e))
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200573
David Garciaeb8943a2021-04-12 12:07:37 +0200574 self.log.info("K8s proxy charm installed")
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200575 ee_id = N2VCJujuConnector._build_ee_id(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000576 model_name=model_name, application_name=application_name, machine_id="k8s"
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200577 )
Dominik Fleischmann7ace6fa2020-06-29 16:16:28 +0200578
579 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
580
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200581 return ee_id
582
quilesj29114342019-10-29 09:30:44 +0100583 async def get_ee_ssh_public__key(
584 self,
585 ee_id: str,
586 db_dict: dict,
587 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400588 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200589 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100590 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200591 """
592 Get Execution environment ssh public key
593
594 :param: ee_id: the id of the execution environment returned by
595 create_execution_environment or register_execution_environment
596 :param: db_dict: where to write into database when the status changes.
597 It contains a dict with
598 {collection: <str>, filter: {}, path: <str>},
599 e.g. {collection: "nsrs", filter:
600 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
601 :param: progress_timeout: Progress timeout
602 :param: total_timeout: Total timeout
603 :param vca_id: VCA ID
604 :returns: public key of the execution environment
605 For the case of juju proxy charm ssh-layered, it is the one
606 returned by 'get-ssh-public-key' primitive.
607 It raises a N2VC exception if fails
608 """
quilesj29114342019-10-29 09:30:44 +0100609
beierlmf52cb7c2020-04-21 16:36:35 -0400610 self.log.info(
611 (
612 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
613 ).format(ee_id, db_dict)
614 )
David Garciaeb8943a2021-04-12 12:07:37 +0200615 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100616
quilesj29114342019-10-29 09:30:44 +0100617 # check arguments
618 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400619 raise N2VCBadArgumentsException(
620 message="ee_id is mandatory", bad_args=["ee_id"]
621 )
quilesj29114342019-10-29 09:30:44 +0100622 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400623 raise N2VCBadArgumentsException(
624 message="db_dict is mandatory", bad_args=["db_dict"]
625 )
quilesj29114342019-10-29 09:30:44 +0100626
627 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400628 (
629 model_name,
630 application_name,
631 machine_id,
632 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
633 self.log.debug(
634 "model: {}, application: {}, machine: {}".format(
635 model_name, application_name, machine_id
636 )
637 )
638 except Exception:
quilesj29114342019-10-29 09:30:44 +0100639 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400640 message="ee_id={} is not a valid execution environment id".format(
641 ee_id
642 ),
643 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100644 )
645
646 # try to execute ssh layer primitives (if exist):
647 # generate-ssh-key
648 # get-ssh-public-key
649
650 output = None
651
David Garcia4fee80e2020-05-13 12:18:38 +0200652 application_name = N2VCJujuConnector._format_app_name(application_name)
653
quilesj29114342019-10-29 09:30:44 +0100654 # execute action: generate-ssh-key
655 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200656 output, _status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100657 model_name=model_name,
658 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400659 action_name="generate-ssh-key",
quilesj29114342019-10-29 09:30:44 +0100660 db_dict=db_dict,
661 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400662 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100663 )
664 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400665 self.log.info(
666 "Skipping exception while executing action generate-ssh-key: {}".format(
667 e
668 )
669 )
quilesj29114342019-10-29 09:30:44 +0100670
671 # execute action: get-ssh-public-key
672 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200673 output, _status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100674 model_name=model_name,
675 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400676 action_name="get-ssh-public-key",
quilesj29114342019-10-29 09:30:44 +0100677 db_dict=db_dict,
678 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400679 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100680 )
681 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400682 msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100683 self.log.info(msg)
David Garcia4fee80e2020-05-13 12:18:38 +0200684 raise N2VCExecutionException(e, primitive_name="get-ssh-public-key")
quilesj29114342019-10-29 09:30:44 +0100685
686 # return public key if exists
David Garcia9ae8fa52019-12-09 18:50:03 +0100687 return output["pubkey"] if "pubkey" in output else output
quilesj29114342019-10-29 09:30:44 +0100688
David Garciaeb8943a2021-04-12 12:07:37 +0200689 async def get_metrics(
690 self, model_name: str, application_name: str, vca_id: str = None
691 ) -> dict:
692 """
693 Get metrics from application
694
695 :param: model_name: Model name
696 :param: application_name: Application name
697 :param: vca_id: VCA ID
698
699 :return: Dictionary with obtained metrics
700 """
701 libjuju = await self._get_libjuju(vca_id)
702 return await libjuju.get_metrics(model_name, application_name)
David Garcia85755d12020-09-21 19:51:23 +0200703
quilesj29114342019-10-29 09:30:44 +0100704 async def add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000705 self, provider: RelationEndpoint, requirer: RelationEndpoint
quilesj29114342019-10-29 09:30:44 +0100706 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200707 """
708 Add relation between two charmed endpoints
quilesj29114342019-10-29 09:30:44 +0100709
David Garcia582b9232021-10-26 12:30:44 +0200710 :param: provider: Provider relation endpoint
711 :param: requirer: Requirer relation endpoint
David Garciaeb8943a2021-04-12 12:07:37 +0200712 """
David Garcia582b9232021-10-26 12:30:44 +0200713 self.log.debug(f"adding new relation between {provider} and {requirer}")
714 cross_model_relation = (
715 provider.model_name != requirer.model_name
Patricia Reinoso085942e2022-12-05 16:55:51 +0000716 or provider.vca_id != requirer.vca_id
beierlmf52cb7c2020-04-21 16:36:35 -0400717 )
quilesj29114342019-10-29 09:30:44 +0100718 try:
David Garcia582b9232021-10-26 12:30:44 +0200719 if cross_model_relation:
720 # Cross-model relation
721 provider_libjuju = await self._get_libjuju(provider.vca_id)
722 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
723 offer = await provider_libjuju.offer(provider)
724 if offer:
725 saas_name = await requirer_libjuju.consume(
726 requirer.model_name, offer, provider_libjuju
727 )
728 await requirer_libjuju.add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000729 requirer.model_name, requirer.endpoint, saas_name
David Garcia582b9232021-10-26 12:30:44 +0200730 )
731 else:
732 # Standard relation
733 vca_id = provider.vca_id
734 model = provider.model_name
735 libjuju = await self._get_libjuju(vca_id)
736 # add juju relations between two applications
737 await libjuju.add_relation(
738 model_name=model,
739 endpoint_1=provider.endpoint,
740 endpoint_2=requirer.endpoint,
741 )
quilesj29114342019-10-29 09:30:44 +0100742 except Exception as e:
David Garcia582b9232021-10-26 12:30:44 +0200743 message = f"Error adding relation between {provider} and {requirer}: {e}"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100744 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100745 raise N2VCException(message=message)
746
beierlmf52cb7c2020-04-21 16:36:35 -0400747 async def remove_relation(self):
quilesj29114342019-10-29 09:30:44 +0100748 # TODO
beierlmf52cb7c2020-04-21 16:36:35 -0400749 self.log.info("Method not implemented yet")
750 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100751
beierlmf52cb7c2020-04-21 16:36:35 -0400752 async def deregister_execution_environments(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400753 self.log.info("Method not implemented yet")
754 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100755
756 async def delete_namespace(
David Garciaeb8943a2021-04-12 12:07:37 +0200757 self,
758 namespace: str,
759 db_dict: dict = None,
760 total_timeout: float = None,
761 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100762 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200763 """
764 Remove a network scenario and its execution environments
765 :param: namespace: [<nsi-id>].<ns-id>
766 :param: db_dict: where to write into database when the status changes.
767 It contains a dict with
768 {collection: <str>, filter: {}, path: <str>},
769 e.g. {collection: "nsrs", filter:
770 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
771 :param: total_timeout: Total timeout
772 :param: vca_id: VCA ID
773 """
beierlmf52cb7c2020-04-21 16:36:35 -0400774 self.log.info("Deleting namespace={}".format(namespace))
David Garcia1423ffa2022-05-04 15:33:03 +0200775 will_not_delete = False
776 if namespace not in self.delete_namespace_locks:
Guillermo Calvino474fd952023-04-28 11:51:43 +0200777 self.delete_namespace_locks[namespace] = asyncio.Lock()
David Garciacd986062022-05-05 09:46:06 +0200778 delete_lock = self.delete_namespace_locks[namespace]
quilesj29114342019-10-29 09:30:44 +0100779
David Garciacd986062022-05-05 09:46:06 +0200780 while delete_lock.locked():
David Garcia1423ffa2022-05-04 15:33:03 +0200781 will_not_delete = True
782 await asyncio.sleep(0.1)
quilesj29114342019-10-29 09:30:44 +0100783
David Garcia1423ffa2022-05-04 15:33:03 +0200784 if will_not_delete:
785 self.log.info("Namespace {} deleted by another worker.".format(namespace))
786 return
787
788 try:
David Garciacd986062022-05-05 09:46:06 +0200789 async with delete_lock:
David Garcia1423ffa2022-05-04 15:33:03 +0200790 libjuju = await self._get_libjuju(vca_id)
791
792 # check arguments
793 if namespace is None:
794 raise N2VCBadArgumentsException(
795 message="namespace is mandatory", bad_args=["namespace"]
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200796 )
quilesj29114342019-10-29 09:30:44 +0100797
David Garcia1423ffa2022-05-04 15:33:03 +0200798 (
799 _nsi_id,
800 ns_id,
801 _vnf_id,
802 _vdu_id,
803 _vdu_count,
804 ) = self._get_namespace_components(namespace=namespace)
805 if ns_id is not None:
806 try:
807 models = await libjuju.list_models(contains=ns_id)
808 for model in models:
809 await libjuju.destroy_model(
810 model_name=model, total_timeout=total_timeout
811 )
812 except Exception as e:
David Garcia1608b562022-05-06 12:26:20 +0200813 self.log.error(f"Error deleting namespace {namespace} : {e}")
David Garcia1423ffa2022-05-04 15:33:03 +0200814 raise N2VCException(
815 message="Error deleting namespace {} : {}".format(
816 namespace, e
817 )
818 )
819 else:
820 raise N2VCBadArgumentsException(
821 message="only ns_id is permitted to delete yet",
822 bad_args=["namespace"],
823 )
David Garcia1608b562022-05-06 12:26:20 +0200824 except Exception as e:
825 self.log.error(f"Error deleting namespace {namespace} : {e}")
826 raise e
David Garcia1423ffa2022-05-04 15:33:03 +0200827 finally:
828 self.delete_namespace_locks.pop(namespace)
beierlmf52cb7c2020-04-21 16:36:35 -0400829 self.log.info("Namespace {} deleted".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100830
831 async def delete_execution_environment(
David Garciaeb8943a2021-04-12 12:07:37 +0200832 self,
833 ee_id: str,
834 db_dict: dict = None,
835 total_timeout: float = None,
836 scaling_in: bool = False,
aktasfa02f8a2021-07-29 17:41:40 +0300837 vca_type: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200838 vca_id: str = None,
Dario Faccin4ab954c2023-06-16 10:21:38 +0200839 application_to_delete: str = None,
quilesj29114342019-10-29 09:30:44 +0100840 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200841 """
842 Delete an execution environment
843 :param str ee_id: id of the execution environment to delete
844 :param dict db_dict: where to write into database when the status changes.
845 It contains a dict with
846 {collection: <str>, filter: {}, path: <str>},
847 e.g. {collection: "nsrs", filter:
848 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
Dario Faccin4ab954c2023-06-16 10:21:38 +0200849 :param total_timeout: Total timeout
850 :param scaling_in: Boolean to indicate if it is a scaling in operation
851 :param vca_type: VCA type
852 :param vca_id: VCA ID
853 :param application_to_delete: name of the single application to be deleted
David Garciaeb8943a2021-04-12 12:07:37 +0200854 """
beierlmf52cb7c2020-04-21 16:36:35 -0400855 self.log.info("Deleting execution environment ee_id={}".format(ee_id))
David Garciaeb8943a2021-04-12 12:07:37 +0200856 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100857
quilesj29114342019-10-29 09:30:44 +0100858 # check arguments
859 if ee_id is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400860 raise N2VCBadArgumentsException(
861 message="ee_id is mandatory", bad_args=["ee_id"]
862 )
quilesj29114342019-10-29 09:30:44 +0100863
aktasfa02f8a2021-07-29 17:41:40 +0300864 model_name, application_name, machine_id = self._get_ee_id_components(
beierlmf52cb7c2020-04-21 16:36:35 -0400865 ee_id=ee_id
866 )
quilesj29114342019-10-29 09:30:44 +0100867 try:
Dario Faccin4ab954c2023-06-16 10:21:38 +0200868 if application_to_delete == application_name:
869 # destroy the application
870 await libjuju.destroy_application(
871 model_name=model_name,
872 application_name=application_name,
873 total_timeout=total_timeout,
874 )
875 # if model is empty delete it
876 controller = await libjuju.get_controller()
877 model = await libjuju.get_model(
878 controller=controller,
879 model_name=model_name,
880 )
881 if not model.applications:
882 self.log.info("Model {} is empty, deleting it".format(model_name))
883 await libjuju.destroy_model(
884 model_name=model_name,
885 total_timeout=total_timeout,
886 )
887 elif not scaling_in:
aktasd1d55412021-02-21 19:36:20 +0300888 # destroy the model
David Garciaeb8943a2021-04-12 12:07:37 +0200889 await libjuju.destroy_model(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000890 model_name=model_name, total_timeout=total_timeout
aktasd1d55412021-02-21 19:36:20 +0300891 )
aktasfa02f8a2021-07-29 17:41:40 +0300892 elif vca_type == "native_charm" and scaling_in:
893 # destroy the unit in the application
894 await libjuju.destroy_unit(
895 application_name=application_name,
896 model_name=model_name,
897 machine_id=machine_id,
aktasfa02f8a2021-07-29 17:41:40 +0300898 total_timeout=total_timeout,
899 )
aktasd1d55412021-02-21 19:36:20 +0300900 else:
aktasd1d55412021-02-21 19:36:20 +0300901 # destroy the application
David Garciaeb8943a2021-04-12 12:07:37 +0200902 await libjuju.destroy_application(
aktas56120292021-02-26 15:32:39 +0300903 model_name=model_name,
904 application_name=application_name,
905 total_timeout=total_timeout,
906 )
quilesj29114342019-10-29 09:30:44 +0100907 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400908 raise N2VCException(
909 message=(
910 "Error deleting execution environment {} (application {}) : {}"
911 ).format(ee_id, application_name, e)
912 )
quilesj29114342019-10-29 09:30:44 +0100913
beierlmf52cb7c2020-04-21 16:36:35 -0400914 self.log.info("Execution environment {} deleted".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100915
916 async def exec_primitive(
beierlmf52cb7c2020-04-21 16:36:35 -0400917 self,
918 ee_id: str,
919 primitive_name: str,
920 params_dict: dict,
921 db_dict: dict = None,
922 progress_timeout: float = None,
923 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200924 vca_id: str = None,
aktasfa02f8a2021-07-29 17:41:40 +0300925 vca_type: str = None,
quilesj29114342019-10-29 09:30:44 +0100926 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200927 """
928 Execute a primitive in the execution environment
929
930 :param: ee_id: the one returned by create_execution_environment or
931 register_execution_environment
932 :param: primitive_name: must be one defined in the software. There is one
933 called 'config', where, for the proxy case, the 'credentials' of VM are
934 provided
935 :param: params_dict: parameters of the action
936 :param: db_dict: where to write into database when the status changes.
937 It contains a dict with
938 {collection: <str>, filter: {}, path: <str>},
939 e.g. {collection: "nsrs", filter:
940 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
941 :param: progress_timeout: Progress timeout
942 :param: total_timeout: Total timeout
943 :param: vca_id: VCA ID
aktasfa02f8a2021-07-29 17:41:40 +0300944 :param: vca_type: VCA type
David Garciaeb8943a2021-04-12 12:07:37 +0200945 :returns str: primitive result, if ok. It raises exceptions in case of fail
946 """
quilesj29114342019-10-29 09:30:44 +0100947
beierlmf52cb7c2020-04-21 16:36:35 -0400948 self.log.info(
949 "Executing primitive: {} on ee: {}, params: {}".format(
950 primitive_name, ee_id, params_dict
951 )
952 )
David Garciaeb8943a2021-04-12 12:07:37 +0200953 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100954
quilesj29114342019-10-29 09:30:44 +0100955 # check arguments
956 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400957 raise N2VCBadArgumentsException(
958 message="ee_id is mandatory", bad_args=["ee_id"]
959 )
quilesj29114342019-10-29 09:30:44 +0100960 if primitive_name is None or len(primitive_name) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400961 raise N2VCBadArgumentsException(
962 message="action_name is mandatory", bad_args=["action_name"]
963 )
quilesj29114342019-10-29 09:30:44 +0100964 if params_dict is None:
965 params_dict = dict()
966
967 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400968 (
969 model_name,
970 application_name,
aktasfa02f8a2021-07-29 17:41:40 +0300971 machine_id,
beierlmf52cb7c2020-04-21 16:36:35 -0400972 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
aktasfa02f8a2021-07-29 17:41:40 +0300973 # To run action on the leader unit in libjuju.execute_action function,
974 # machine_id must be set to None if vca_type is not native_charm
975 if vca_type != "native_charm":
976 machine_id = None
quilesj29114342019-10-29 09:30:44 +0100977 except Exception:
978 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400979 message="ee_id={} is not a valid execution environment id".format(
980 ee_id
981 ),
982 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100983 )
984
beierlmf52cb7c2020-04-21 16:36:35 -0400985 if primitive_name == "config":
quilesj29114342019-10-29 09:30:44 +0100986 # Special case: config primitive
987 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200988 await libjuju.configure_application(
quilesj29114342019-10-29 09:30:44 +0100989 model_name=model_name,
990 application_name=application_name,
991 config=params_dict,
quilesj29114342019-10-29 09:30:44 +0100992 )
David Garciaeb8943a2021-04-12 12:07:37 +0200993 actions = await libjuju.get_actions(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000994 application_name=application_name, model_name=model_name
David Garcia4fee80e2020-05-13 12:18:38 +0200995 )
996 self.log.debug(
997 "Application {} has these actions: {}".format(
998 application_name, actions
999 )
1000 )
1001 if "verify-ssh-credentials" in actions:
1002 # execute verify-credentials
1003 num_retries = 20
1004 retry_timeout = 15.0
1005 for _ in range(num_retries):
1006 try:
1007 self.log.debug("Executing action verify-ssh-credentials...")
David Garciaeb8943a2021-04-12 12:07:37 +02001008 output, ok = await libjuju.execute_action(
David Garcia4fee80e2020-05-13 12:18:38 +02001009 model_name=model_name,
1010 application_name=application_name,
1011 action_name="verify-ssh-credentials",
1012 db_dict=db_dict,
1013 progress_timeout=progress_timeout,
1014 total_timeout=total_timeout,
1015 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +02001016
1017 if ok == "failed":
1018 self.log.debug(
1019 "Error executing verify-ssh-credentials: {}. Retrying..."
1020 )
1021 await asyncio.sleep(retry_timeout)
1022
1023 continue
David Garcia4fee80e2020-05-13 12:18:38 +02001024 self.log.debug("Result: {}, output: {}".format(ok, output))
1025 break
1026 except asyncio.CancelledError:
1027 raise
David Garcia4fee80e2020-05-13 12:18:38 +02001028 else:
1029 self.log.error(
1030 "Error executing verify-ssh-credentials after {} retries. ".format(
1031 num_retries
1032 )
1033 )
1034 else:
1035 msg = "Action verify-ssh-credentials does not exist in application {}".format(
1036 application_name
1037 )
1038 self.log.debug(msg=msg)
quilesj29114342019-10-29 09:30:44 +01001039 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001040 self.log.error("Error configuring juju application: {}".format(e))
quilesj29114342019-10-29 09:30:44 +01001041 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -04001042 message="Error configuring application into ee={} : {}".format(
1043 ee_id, e
1044 ),
1045 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +01001046 )
beierlmf52cb7c2020-04-21 16:36:35 -04001047 return "CONFIG OK"
quilesj29114342019-10-29 09:30:44 +01001048 else:
1049 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001050 output, status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +01001051 model_name=model_name,
1052 application_name=application_name,
1053 action_name=primitive_name,
1054 db_dict=db_dict,
aktasfa02f8a2021-07-29 17:41:40 +03001055 machine_id=machine_id,
quilesj29114342019-10-29 09:30:44 +01001056 progress_timeout=progress_timeout,
1057 total_timeout=total_timeout,
David Garcia582b9232021-10-26 12:30:44 +02001058 **params_dict,
quilesj29114342019-10-29 09:30:44 +01001059 )
beierlmf52cb7c2020-04-21 16:36:35 -04001060 if status == "completed":
quilesj29114342019-10-29 09:30:44 +01001061 return output
1062 else:
Mark Beierl015abee2022-08-19 15:02:24 -04001063 if "output" in output:
1064 raise Exception(f'{status}: {output["output"]}')
1065 else:
1066 raise Exception(
1067 f"{status}: No further information received from action"
1068 )
1069
quilesj29114342019-10-29 09:30:44 +01001070 except Exception as e:
Mark Beierl015abee2022-08-19 15:02:24 -04001071 self.log.error(f"Error executing primitive {primitive_name}: {e}")
quilesj29114342019-10-29 09:30:44 +01001072 raise N2VCExecutionException(
Mark Beierl015abee2022-08-19 15:02:24 -04001073 message=f"Error executing primitive {primitive_name} in ee={ee_id}: {e}",
beierlmf52cb7c2020-04-21 16:36:35 -04001074 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +01001075 )
1076
aticig8070c3c2022-04-18 00:31:42 +03001077 async def upgrade_charm(
1078 self,
1079 ee_id: str = None,
1080 path: str = None,
1081 charm_id: str = None,
1082 charm_type: str = None,
1083 timeout: float = None,
1084 ) -> str:
1085 """This method upgrade charms in VNFs
1086
1087 Args:
1088 ee_id: Execution environment id
1089 path: Local path to the charm
1090 charm_id: charm-id
1091 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
1092 timeout: (Float) Timeout for the ns update operation
1093
1094 Returns:
1095 The output of the update operation if status equals to "completed"
1096
1097 """
1098 self.log.info("Upgrading charm: {} on ee: {}".format(path, ee_id))
1099 libjuju = await self._get_libjuju(charm_id)
1100
1101 # check arguments
1102 if ee_id is None or len(ee_id) == 0:
1103 raise N2VCBadArgumentsException(
1104 message="ee_id is mandatory", bad_args=["ee_id"]
1105 )
1106 try:
1107 (
1108 model_name,
1109 application_name,
1110 machine_id,
1111 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
1112
1113 except Exception:
1114 raise N2VCBadArgumentsException(
1115 message="ee_id={} is not a valid execution environment id".format(
1116 ee_id
1117 ),
1118 bad_args=["ee_id"],
1119 )
1120
1121 try:
aticig8070c3c2022-04-18 00:31:42 +03001122 await libjuju.upgrade_charm(
1123 application_name=application_name,
1124 path=path,
1125 model_name=model_name,
1126 total_timeout=timeout,
1127 )
1128
1129 return f"Charm upgraded with application name {application_name}"
1130
1131 except Exception as e:
1132 self.log.error("Error upgrading charm {}: {}".format(path, e))
1133
1134 raise N2VCException(
1135 message="Error upgrading charm {} in ee={} : {}".format(path, ee_id, e)
1136 )
1137
David Garciaeb8943a2021-04-12 12:07:37 +02001138 async def disconnect(self, vca_id: str = None):
1139 """
1140 Disconnect from VCA
1141
1142 :param: vca_id: VCA ID
1143 """
beierlmf52cb7c2020-04-21 16:36:35 -04001144 self.log.info("closing juju N2VC...")
David Garciaeb8943a2021-04-12 12:07:37 +02001145 libjuju = await self._get_libjuju(vca_id)
David Garcia4fee80e2020-05-13 12:18:38 +02001146 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001147 await libjuju.disconnect()
David Garcia4fee80e2020-05-13 12:18:38 +02001148 except Exception as e:
1149 raise N2VCConnectionException(
David Garciaeb8943a2021-04-12 12:07:37 +02001150 message="Error disconnecting controller: {}".format(e),
1151 url=libjuju.vca_connection.data.endpoints,
David Garcia4fee80e2020-05-13 12:18:38 +02001152 )
quilesj29114342019-10-29 09:30:44 +01001153
1154 """
David Garcia4fee80e2020-05-13 12:18:38 +02001155####################################################################################
1156################################### P R I V A T E ##################################
1157####################################################################################
quilesj29114342019-10-29 09:30:44 +01001158 """
1159
David Garciaeb8943a2021-04-12 12:07:37 +02001160 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
1161 """
1162 Get libjuju object
1163
1164 :param: vca_id: VCA ID
1165 If None, get a libjuju object with a Connection to the default VCA
1166 Else, geta libjuju object with a Connection to the specified VCA
1167 """
1168 if not vca_id:
1169 while self.loading_libjuju.locked():
1170 await asyncio.sleep(0.1)
1171 if not self.libjuju:
1172 async with self.loading_libjuju:
1173 vca_connection = await get_connection(self._store)
Mark Beierl2c3c1462023-05-15 16:17:02 -04001174 self.libjuju = Libjuju(vca_connection, log=self.log)
David Garciaeb8943a2021-04-12 12:07:37 +02001175 return self.libjuju
1176 else:
1177 vca_connection = await get_connection(self._store, vca_id)
Mark Beierl2c3c1462023-05-15 16:17:02 -04001178 return Libjuju(vca_connection, log=self.log, n2vc=self)
David Garciaeb8943a2021-04-12 12:07:37 +02001179
beierlmf52cb7c2020-04-21 16:36:35 -04001180 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +01001181 # write ee_id to database: _admin.deployed.VCA.x
1182 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001183 the_table = db_dict["collection"]
1184 the_filter = db_dict["filter"]
1185 the_path = db_dict["path"]
1186 if not the_path[-1] == ".":
1187 the_path = the_path + "."
1188 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001189 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +01001190 self.db.set_one(
1191 table=the_table,
1192 q_filter=the_filter,
1193 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -04001194 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +01001195 )
tierno8ff11992020-03-26 09:51:11 +00001196 except asyncio.CancelledError:
1197 raise
quilesj29114342019-10-29 09:30:44 +01001198 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001199 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +01001200
1201 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001202 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +01001203 """
1204 Build an execution environment id form model, application and machine
1205 :param model_name:
1206 :param application_name:
1207 :param machine_id:
1208 :return:
1209 """
1210 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -04001211 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +01001212
1213 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001214 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +01001215 """
1216 Get model, application and machine components from an execution environment id
1217 :param ee_id:
1218 :return: model_name, application_name, machine_id
1219 """
1220
David Garcia582b9232021-10-26 12:30:44 +02001221 return get_ee_id_components(ee_id)
quilesj29114342019-10-29 09:30:44 +01001222
aticig01244642022-07-28 01:12:03 +03001223 @staticmethod
1224 def _find_charm_level(vnf_id: str, vdu_id: str) -> str:
1225 """Decides the charm level.
1226 Args:
1227 vnf_id (str): VNF id
1228 vdu_id (str): VDU id
1229
1230 Returns:
1231 charm_level (str): ns-level or vnf-level or vdu-level
quilesj29114342019-10-29 09:30:44 +01001232 """
aticig01244642022-07-28 01:12:03 +03001233 if vdu_id and not vnf_id:
1234 raise N2VCException(message="If vdu-id exists, vnf-id should be provided.")
1235 if vnf_id and vdu_id:
1236 return "vdu-level"
1237 if vnf_id and not vdu_id:
1238 return "vnf-level"
1239 if not vnf_id and not vdu_id:
1240 return "ns-level"
1241
1242 @staticmethod
1243 def _generate_backward_compatible_application_name(
1244 vnf_id: str, vdu_id: str, vdu_count: str
1245 ) -> str:
1246 """Generate backward compatible application name
1247 by limiting the app name to 50 characters.
1248
1249 Args:
1250 vnf_id (str): VNF ID
1251 vdu_id (str): VDU ID
1252 vdu_count (str): vdu-count-index
1253
1254 Returns:
1255 application_name (str): generated application name
1256
quilesj29114342019-10-29 09:30:44 +01001257 """
quilesj29114342019-10-29 09:30:44 +01001258 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001259 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001260 else:
Adam Israel18046072019-12-08 21:44:29 -05001261 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001262 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001263
1264 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001265 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001266 else:
Adam Israel18046072019-12-08 21:44:29 -05001267 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001268 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001269
1270 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001271 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001272 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001273 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001274
Pedro Escaleira0ebadd82022-03-21 17:54:45 +00001275 # Generate a random suffix with 5 characters (the default size used by K8s)
1276 random_suffix = generate_random_alfanum_string(size=5)
1277
1278 application_name = "app-{}{}{}-{}".format(
1279 vnf_id, vdu_id, vdu_count, random_suffix
1280 )
aticig01244642022-07-28 01:12:03 +03001281 return application_name
1282
1283 @staticmethod
Gulsum Atici552a6012022-10-17 14:40:39 +03001284 def _get_vca_record(search_key: str, vca_records: list, vdu_id: str) -> dict:
1285 """Get the correct VCA record dict depending on the search key
1286
1287 Args:
1288 search_key (str): keyword to find the correct VCA record
1289 vca_records (list): All VCA records as list
1290 vdu_id (str): VDU ID
1291
1292 Returns:
1293 vca_record (dict): Dictionary which includes the correct VCA record
1294
1295 """
1296 return next(
1297 filter(lambda record: record[search_key] == vdu_id, vca_records), {}
1298 )
1299
1300 @staticmethod
aticig01244642022-07-28 01:12:03 +03001301 def _generate_application_name(
1302 charm_level: str,
1303 vnfrs: dict,
1304 vca_records: list,
1305 vnf_count: str = None,
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001306 vdu_id: str = None,
aticig01244642022-07-28 01:12:03 +03001307 vdu_count: str = None,
1308 ) -> str:
1309 """Generate application name to make the relevant charm of VDU/KDU
1310 in the VNFD descriptor become clearly visible.
1311 Limiting the app name to 50 characters.
1312
1313 Args:
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001314 charm_level (str): level of charm
1315 vnfrs (dict): vnf record dict
aticig01244642022-07-28 01:12:03 +03001316 vca_records (list): db_nsr["_admin"]["deployed"]["VCA"] as list
1317 vnf_count (str): vnf count index
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001318 vdu_id (str): VDU ID
aticig01244642022-07-28 01:12:03 +03001319 vdu_count (str): vdu count index
1320
1321 Returns:
1322 application_name (str): generated application name
1323
1324 """
1325 application_name = ""
1326 if charm_level == "ns-level":
1327 if len(vca_records) != 1:
1328 raise N2VCException(message="One VCA record is expected.")
1329 # Only one VCA record is expected if it's ns-level charm.
1330 # Shorten the charm name to its first 40 characters.
1331 charm_name = vca_records[0]["charm_name"][:40]
1332 if not charm_name:
1333 raise N2VCException(message="Charm name should be provided.")
1334 application_name = charm_name + "-ns"
1335
1336 elif charm_level == "vnf-level":
1337 if len(vca_records) < 1:
1338 raise N2VCException(message="One or more VCA record is expected.")
1339 # If VNF is scaled, more than one VCA record may be included in vca_records
1340 # but ee_descriptor_id is same.
1341 # Shorten the ee_descriptor_id and member-vnf-index-ref
1342 # to first 12 characters.
1343 application_name = (
1344 vca_records[0]["ee_descriptor_id"][:12]
1345 + "-"
1346 + vnf_count
1347 + "-"
1348 + vnfrs["member-vnf-index-ref"][:12]
1349 + "-vnf"
1350 )
1351 elif charm_level == "vdu-level":
1352 if len(vca_records) < 1:
1353 raise N2VCException(message="One or more VCA record is expected.")
aticig4c856b32022-08-19 19:58:13 +03001354
1355 # Charms are also used for deployments with Helm charts.
1356 # If deployment unit is a Helm chart/KDU,
1357 # vdu_profile_id and vdu_count will be empty string.
aticig4c856b32022-08-19 19:58:13 +03001358 if vdu_count is None:
1359 vdu_count = ""
1360
aticig01244642022-07-28 01:12:03 +03001361 # If vnf/vdu is scaled, more than one VCA record may be included in vca_records
1362 # but ee_descriptor_id is same.
1363 # Shorten the ee_descriptor_id, member-vnf-index-ref and vdu_profile_id
1364 # to first 12 characters.
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001365 if not vdu_id:
1366 raise N2VCException(message="vdu-id should be provided.")
Gulsum Atici552a6012022-10-17 14:40:39 +03001367
1368 vca_record = N2VCJujuConnector._get_vca_record(
1369 "vdu_id", vca_records, vdu_id
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001370 )
Gulsum Atici552a6012022-10-17 14:40:39 +03001371
1372 if not vca_record:
1373 vca_record = N2VCJujuConnector._get_vca_record(
1374 "kdu_name", vca_records, vdu_id
1375 )
1376
aticig01244642022-07-28 01:12:03 +03001377 application_name = (
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001378 vca_record["ee_descriptor_id"][:12]
aticig01244642022-07-28 01:12:03 +03001379 + "-"
1380 + vnf_count
1381 + "-"
1382 + vnfrs["member-vnf-index-ref"][:12]
1383 + "-"
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001384 + vdu_id[:12]
aticig01244642022-07-28 01:12:03 +03001385 + "-"
1386 + vdu_count
1387 + "-vdu"
1388 )
1389
1390 return application_name
1391
1392 def _get_vnf_count_and_record(
1393 self, charm_level: str, vnf_id_and_count: str
1394 ) -> Tuple[str, dict]:
1395 """Get the vnf count and VNF record depend on charm level
1396
1397 Args:
1398 charm_level (str)
1399 vnf_id_and_count (str)
1400
1401 Returns:
1402 (vnf_count (str), db_vnfr(dict)) as Tuple
1403
1404 """
1405 vnf_count = ""
1406 db_vnfr = {}
1407
1408 if charm_level in ("vnf-level", "vdu-level"):
1409 vnf_id = "-".join(vnf_id_and_count.split("-")[:-1])
1410 vnf_count = vnf_id_and_count.split("-")[-1]
1411 db_vnfr = self.db.get_one("vnfrs", {"_id": vnf_id})
1412
1413 # If the charm is ns level, it returns empty vnf_count and db_vnfr
1414 return vnf_count, db_vnfr
1415
1416 @staticmethod
1417 def _get_vca_records(charm_level: str, db_nsr: dict, db_vnfr: dict) -> list:
1418 """Get the VCA records from db_nsr dict
1419
1420 Args:
1421 charm_level (str): level of charm
1422 db_nsr (dict): NS record from database
1423 db_vnfr (dict): VNF record from database
1424
1425 Returns:
1426 vca_records (list): List of VCA record dictionaries
1427
1428 """
1429 vca_records = {}
1430 if charm_level == "ns-level":
1431 vca_records = list(
1432 filter(
1433 lambda vca_record: vca_record["target_element"] == "ns",
1434 db_nsr["_admin"]["deployed"]["VCA"],
1435 )
1436 )
1437 elif charm_level in ["vnf-level", "vdu-level"]:
1438 vca_records = list(
1439 filter(
1440 lambda vca_record: vca_record["member-vnf-index"]
1441 == db_vnfr["member-vnf-index-ref"],
1442 db_nsr["_admin"]["deployed"]["VCA"],
1443 )
1444 )
1445
1446 return vca_records
1447
1448 def _get_application_name(self, namespace: str) -> str:
1449 """Build application name from namespace
1450
1451 Application name structure:
1452 NS level: <charm-name>-ns
1453 VNF level: <ee-name>-z<vnf-ordinal-scale-number>-<vnf-profile-id>-vnf
1454 VDU level: <ee-name>-z<vnf-ordinal-scale-number>-<vnf-profile-id>-
1455 <vdu-profile-id>-z<vdu-ordinal-scale-number>-vdu
1456
1457 Application naming for backward compatibility (old structure):
1458 NS level: app-<random_value>
1459 VNF level: app-vnf-<vnf-id>-z<ordinal-scale-number>-<random_value>
1460 VDU level: app-vnf-<vnf-id>-z<vnf-ordinal-scale-number>-vdu-
1461 <vdu-id>-cnt-<vdu-count>-z<vdu-ordinal-scale-number>-<random_value>
1462
1463 Args:
1464 namespace (str)
1465
1466 Returns:
1467 application_name (str)
1468
1469 """
1470 # split namespace components
1471 (
1472 nsi_id,
1473 ns_id,
1474 vnf_id_and_count,
1475 vdu_id,
1476 vdu_count,
1477 ) = self._get_namespace_components(namespace=namespace)
1478
1479 if not ns_id:
1480 raise N2VCException(message="ns-id should be provided.")
1481
1482 charm_level = self._find_charm_level(vnf_id_and_count, vdu_id)
1483 db_nsr = self.db.get_one("nsrs", {"_id": ns_id})
1484 vnf_count, db_vnfr = self._get_vnf_count_and_record(
1485 charm_level, vnf_id_and_count
1486 )
1487 vca_records = self._get_vca_records(charm_level, db_nsr, db_vnfr)
1488
1489 if all("charm_name" in vca_record.keys() for vca_record in vca_records):
1490 application_name = self._generate_application_name(
1491 charm_level,
1492 db_vnfr,
1493 vca_records,
1494 vnf_count=vnf_count,
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001495 vdu_id=vdu_id,
aticig01244642022-07-28 01:12:03 +03001496 vdu_count=vdu_count,
1497 )
1498 else:
1499 application_name = self._generate_backward_compatible_application_name(
1500 vnf_id_and_count, vdu_id, vdu_count
1501 )
quilesj29114342019-10-29 09:30:44 +01001502
1503 return N2VCJujuConnector._format_app_name(application_name)
1504
quilesj29114342019-10-29 09:30:44 +01001505 @staticmethod
1506 def _format_model_name(name: str) -> str:
1507 """Format the name of the model.
1508
1509 Model names may only contain lowercase letters, digits and hyphens
1510 """
1511
beierlmf52cb7c2020-04-21 16:36:35 -04001512 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001513
1514 @staticmethod
1515 def _format_app_name(name: str) -> str:
1516 """Format the name of the application (in order to assure valid application name).
1517
1518 Application names have restrictions (run juju deploy --help):
1519 - contains lowercase letters 'a'-'z'
1520 - contains numbers '0'-'9'
1521 - contains hyphens '-'
1522 - starts with a lowercase letter
1523 - not two or more consecutive hyphens
1524 - after a hyphen, not a group with all numbers
1525 """
1526
1527 def all_numbers(s: str) -> bool:
1528 for c in s:
1529 if not c.isdigit():
1530 return False
1531 return True
1532
beierlmf52cb7c2020-04-21 16:36:35 -04001533 new_name = name.replace("_", "-")
1534 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001535 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001536 while new_name.find("--") >= 0:
1537 new_name = new_name.replace("--", "-")
1538 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001539
1540 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001541 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001542 for i in range(len(groups)):
1543 group = groups[i]
1544 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001545 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001546 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001547 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001548 app_name += group
1549
1550 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001551 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001552
1553 return app_name
David Garciaeb8943a2021-04-12 12:07:37 +02001554
1555 async def validate_vca(self, vca_id: str):
1556 """
1557 Validate a VCA by connecting/disconnecting to/from it
1558
1559 :param: vca_id: VCA ID
1560 """
1561 vca_connection = await get_connection(self._store, vca_id=vca_id)
Mark Beierl2c3c1462023-05-15 16:17:02 -04001562 libjuju = Libjuju(vca_connection, log=self.log, n2vc=self)
David Garciaeb8943a2021-04-12 12:07:37 +02001563 controller = await libjuju.get_controller()
1564 await libjuju.disconnect_controller(controller)