blob: cbca396543017982559fa4d578b0afede7a72b25 [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
David Garcia4fee80e2020-05-13 12:18:38 +020040from n2vc.libjuju import Libjuju
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,
64 loop: object = None,
beierlmf52cb7c2020-04-21 16:36:35 -040065 on_update_db=None,
quilesj29114342019-10-29 09:30:44 +010066 ):
David Garciaeb8943a2021-04-12 12:07:37 +020067 """
68 Constructor
69
70 :param: db: Database object from osm_common
71 :param: fs: Filesystem object from osm_common
72 :param: log: Logger
73 :param: loop: Asyncio loop
74 :param: on_update_db: Callback function to be called for updating the database.
quilesj29114342019-10-29 09:30:44 +010075 """
76
77 # parent class constructor
78 N2VCConnector.__init__(
Patricia Reinoso085942e2022-12-05 16:55:51 +000079 self, db=db, fs=fs, log=log, loop=loop, on_update_db=on_update_db
quilesj29114342019-10-29 09:30:44 +010080 )
81
82 # silence websocket traffic log
beierlmf52cb7c2020-04-21 16:36:35 -040083 logging.getLogger("websockets.protocol").setLevel(logging.INFO)
84 logging.getLogger("juju.client.connection").setLevel(logging.WARN)
85 logging.getLogger("model").setLevel(logging.WARN)
quilesj29114342019-10-29 09:30:44 +010086
beierlmf52cb7c2020-04-21 16:36:35 -040087 self.log.info("Initializing N2VC juju connector...")
quilesj29114342019-10-29 09:30:44 +010088
David Garciaeb8943a2021-04-12 12:07:37 +020089 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
90 self._store = MotorStore(db_uri)
91 self.loading_libjuju = asyncio.Lock(loop=self.loop)
David Garcia1423ffa2022-05-04 15:33:03 +020092 self.delete_namespace_locks = {}
beierlmf52cb7c2020-04-21 16:36:35 -040093 self.log.info("N2VC juju connector initialized")
quilesj29114342019-10-29 09:30:44 +010094
David Garciaeb8943a2021-04-12 12:07:37 +020095 async def get_status(
96 self, namespace: str, yaml_format: bool = True, vca_id: str = None
97 ):
98 """
99 Get status from all juju models from a VCA
100
101 :param namespace: we obtain ns from namespace
102 :param yaml_format: returns a yaml string
103 :param: vca_id: VCA ID from which the status will be retrieved.
104 """
105 # TODO: Review where is this function used. It is not optimal at all to get the status
106 # from all the juju models of a particular VCA. Additionally, these models might
107 # not have been deployed by OSM, in that case we are getting information from
108 # deployments outside of OSM's scope.
quilesj776ab392019-12-12 16:10:54 +0000109
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100110 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200111 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100112
beierlmf52cb7c2020-04-21 16:36:35 -0400113 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
114 namespace=namespace
115 )
quilesj29114342019-10-29 09:30:44 +0100116 # model name is ns_id
117 model_name = ns_id
118 if model_name is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400119 msg = "Namespace {} not valid".format(namespace)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100120 self.log.error(msg)
beierlmf52cb7c2020-04-21 16:36:35 -0400121 raise N2VCBadArgumentsException(msg, ["namespace"])
quilesj29114342019-10-29 09:30:44 +0100122
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200123 status = {}
David Garciaeb8943a2021-04-12 12:07:37 +0200124 models = await libjuju.list_models(contains=ns_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200125
126 for m in models:
David Garciaeb8943a2021-04-12 12:07:37 +0200127 status[m] = await libjuju.get_model_status(m)
128
quilesj776ab392019-12-12 16:10:54 +0000129 if yaml_format:
130 return obj_to_yaml(status)
131 else:
132 return obj_to_dict(status)
quilesj29114342019-10-29 09:30:44 +0100133
David Garciaeb8943a2021-04-12 12:07:37 +0200134 async def update_vca_status(self, vcastatus: dict, vca_id: str = None):
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530135 """
136 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
David Garciaeb8943a2021-04-12 12:07:37 +0200137
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530138 :param vcastatus: dict containing vcaStatus
David Garciaeb8943a2021-04-12 12:07:37 +0200139 :param: vca_id: VCA ID
140
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530141 :return: None
142 """
143 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200144 libjuju = await self._get_libjuju(vca_id)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530145 for model_name in vcastatus:
146 # Adding executed actions
garciadeblas82b591c2021-03-24 09:22:13 +0100147 vcastatus[model_name][
148 "executedActions"
149 ] = await libjuju.get_executed_actions(model_name)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530150 for application in vcastatus[model_name]["applications"]:
151 # Adding application actions
garciadeblas82b591c2021-03-24 09:22:13 +0100152 vcastatus[model_name]["applications"][application][
153 "actions"
154 ] = await libjuju.get_actions(application, model_name)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530155 # Adding application configs
garciadeblas82b591c2021-03-24 09:22:13 +0100156 vcastatus[model_name]["applications"][application][
157 "configs"
158 ] = await libjuju.get_application_configs(model_name, application)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530159 except Exception as e:
160 self.log.debug("Error in updating vca status: {}".format(str(e)))
161
quilesj29114342019-10-29 09:30:44 +0100162 async def create_execution_environment(
163 self,
164 namespace: str,
165 db_dict: dict,
166 reuse_ee_id: str = None,
167 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400168 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200169 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100170 ) -> (str, dict):
David Garciaeb8943a2021-04-12 12:07:37 +0200171 """
172 Create an Execution Environment. Returns when it is created or raises an
173 exception on failing
174
175 :param: namespace: Contains a dot separate string.
176 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
177 :param: db_dict: where to write to database when the status changes.
178 It contains a dictionary with {collection: str, filter: {}, path: str},
179 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
180 "_admin.deployed.VCA.3"}
181 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
182 older environment
183 :param: progress_timeout: Progress timeout
184 :param: total_timeout: Total timeout
185 :param: vca_id: VCA ID
186
187 :returns: id of the new execution environment and credentials for it
188 (credentials can contains hostname, username, etc depending on underlying cloud)
189 """
quilesj29114342019-10-29 09:30:44 +0100190
beierlmf52cb7c2020-04-21 16:36:35 -0400191 self.log.info(
192 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
193 namespace, reuse_ee_id
194 )
195 )
David Garciaeb8943a2021-04-12 12:07:37 +0200196 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100197
quilesj29114342019-10-29 09:30:44 +0100198 machine_id = None
199 if reuse_ee_id:
beierlmf52cb7c2020-04-21 16:36:35 -0400200 model_name, application_name, machine_id = self._get_ee_id_components(
201 ee_id=reuse_ee_id
202 )
quilesj29114342019-10-29 09:30:44 +0100203 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400204 (
205 _nsi_id,
206 ns_id,
207 _vnf_id,
208 _vdu_id,
209 _vdu_count,
210 ) = self._get_namespace_components(namespace=namespace)
quilesj29114342019-10-29 09:30:44 +0100211 # model name is ns_id
212 model_name = ns_id
213 # application name
214 application_name = self._get_application_name(namespace=namespace)
215
beierlmf52cb7c2020-04-21 16:36:35 -0400216 self.log.debug(
217 "model name: {}, application name: {}, machine_id: {}".format(
218 model_name, application_name, machine_id
219 )
220 )
quilesj29114342019-10-29 09:30:44 +0100221
222 # create or reuse a new juju machine
223 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200224 if not await libjuju.model_exists(model_name):
Patricia Reinoso085942e2022-12-05 16:55:51 +0000225 await libjuju.add_model(model_name, libjuju.vca_connection.lxd_cloud)
David Garciaeb8943a2021-04-12 12:07:37 +0200226 machine, new = await libjuju.create_machine(
quilesj29114342019-10-29 09:30:44 +0100227 model_name=model_name,
quilesj29114342019-10-29 09:30:44 +0100228 machine_id=machine_id,
229 db_dict=db_dict,
230 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400231 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100232 )
David Garcia4fee80e2020-05-13 12:18:38 +0200233 # id for the execution environment
234 ee_id = N2VCJujuConnector._build_ee_id(
235 model_name=model_name,
236 application_name=application_name,
237 machine_id=str(machine.entity_id),
238 )
239 self.log.debug("ee_id: {}".format(ee_id))
240
241 if new:
242 # write ee_id in database
243 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
244
quilesj29114342019-10-29 09:30:44 +0100245 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400246 message = "Error creating machine on juju: {}".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100247 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100248 raise N2VCException(message=message)
249
quilesj29114342019-10-29 09:30:44 +0100250 # new machine credentials
Patricia Reinoso085942e2022-12-05 16:55:51 +0000251 credentials = {"hostname": machine.dns_name}
quilesj29114342019-10-29 09:30:44 +0100252
beierlmf52cb7c2020-04-21 16:36:35 -0400253 self.log.info(
254 "Execution environment created. ee_id: {}, credentials: {}".format(
255 ee_id, credentials
256 )
257 )
quilesj29114342019-10-29 09:30:44 +0100258
259 return ee_id, credentials
260
261 async def register_execution_environment(
262 self,
263 namespace: str,
264 credentials: dict,
265 db_dict: dict,
266 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400267 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200268 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100269 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200270 """
271 Register an existing execution environment at the VCA
quilesj29114342019-10-29 09:30:44 +0100272
David Garciaeb8943a2021-04-12 12:07:37 +0200273 :param: namespace: Contains a dot separate string.
274 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
275 :param: credentials: credentials to access the existing execution environment
276 (it can contains hostname, username, path to private key,
277 etc depending on underlying cloud)
278 :param: db_dict: where to write to database when the status changes.
279 It contains a dictionary with {collection: str, filter: {}, path: str},
280 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
281 "_admin.deployed.VCA.3"}
282 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
283 older environment
284 :param: progress_timeout: Progress timeout
285 :param: total_timeout: Total timeout
286 :param: vca_id: VCA ID
287
288 :returns: id of the execution environment
289 """
beierlmf52cb7c2020-04-21 16:36:35 -0400290 self.log.info(
291 "Registering execution environment. namespace={}, credentials={}".format(
292 namespace, credentials
293 )
294 )
David Garciaeb8943a2021-04-12 12:07:37 +0200295 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100296
297 if credentials is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400298 raise N2VCBadArgumentsException(
299 message="credentials are mandatory", bad_args=["credentials"]
300 )
301 if credentials.get("hostname"):
302 hostname = credentials["hostname"]
quilesj29114342019-10-29 09:30:44 +0100303 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400304 raise N2VCBadArgumentsException(
305 message="hostname is mandatory", bad_args=["credentials.hostname"]
306 )
307 if credentials.get("username"):
308 username = credentials["username"]
quilesj29114342019-10-29 09:30:44 +0100309 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400310 raise N2VCBadArgumentsException(
311 message="username is mandatory", bad_args=["credentials.username"]
312 )
313 if "private_key_path" in credentials:
314 private_key_path = credentials["private_key_path"]
quilesj29114342019-10-29 09:30:44 +0100315 else:
316 # if not passed as argument, use generated private key path
317 private_key_path = self.private_key_path
318
beierlmf52cb7c2020-04-21 16:36:35 -0400319 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
320 namespace=namespace
321 )
quilesj29114342019-10-29 09:30:44 +0100322
323 # model name
324 model_name = ns_id
325 # application name
326 application_name = self._get_application_name(namespace=namespace)
327
328 # register machine on juju
329 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200330 if not await libjuju.model_exists(model_name):
Patricia Reinoso085942e2022-12-05 16:55:51 +0000331 await libjuju.add_model(model_name, libjuju.vca_connection.lxd_cloud)
David Garciaeb8943a2021-04-12 12:07:37 +0200332 machine_id = await libjuju.provision_machine(
quilesj29114342019-10-29 09:30:44 +0100333 model_name=model_name,
334 hostname=hostname,
335 username=username,
336 private_key_path=private_key_path,
337 db_dict=db_dict,
338 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400339 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100340 )
341 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400342 self.log.error("Error registering machine: {}".format(e))
343 raise N2VCException(
344 message="Error registering machine on juju: {}".format(e)
345 )
quilesjac4e0de2019-11-27 16:12:02 +0000346
beierlmf52cb7c2020-04-21 16:36:35 -0400347 self.log.info("Machine registered: {}".format(machine_id))
quilesj29114342019-10-29 09:30:44 +0100348
349 # id for the execution environment
350 ee_id = N2VCJujuConnector._build_ee_id(
351 model_name=model_name,
352 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400353 machine_id=str(machine_id),
quilesj29114342019-10-29 09:30:44 +0100354 )
355
beierlmf52cb7c2020-04-21 16:36:35 -0400356 self.log.info("Execution environment registered. ee_id: {}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100357
358 return ee_id
359
aktasfa02f8a2021-07-29 17:41:40 +0300360 # In case of native_charm is being deployed, if JujuApplicationExists error happens
361 # it will try to add_unit
David Garcia4e1e62b2021-08-18 17:35:50 +0200362 @retry(attempts=3, delay=5, retry_exceptions=(N2VCApplicationExists,), timeout=None)
quilesj29114342019-10-29 09:30:44 +0100363 async def install_configuration_sw(
364 self,
365 ee_id: str,
366 artifact_path: str,
367 db_dict: dict,
368 progress_timeout: float = None,
David Garciadfaa6e82020-04-01 16:06:39 +0200369 total_timeout: float = None,
370 config: dict = None,
David Garciaf8a9d462020-03-25 18:19:02 +0100371 num_units: int = 1,
David Garciaeb8943a2021-04-12 12:07:37 +0200372 vca_id: str = None,
aktasfa02f8a2021-07-29 17:41:40 +0300373 scaling_out: bool = False,
374 vca_type: str = None,
quilesj29114342019-10-29 09:30:44 +0100375 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200376 """
377 Install the software inside the execution environment identified by ee_id
378
379 :param: ee_id: the id of the execution environment returned by
380 create_execution_environment or register_execution_environment
381 :param: artifact_path: where to locate the artifacts (parent folder) using
382 the self.fs
383 the final artifact path will be a combination of this
384 artifact_path and additional string from the config_dict
385 (e.g. charm name)
386 :param: db_dict: where to write into database when the status changes.
387 It contains a dict with
388 {collection: <str>, filter: {}, path: <str>},
389 e.g. {collection: "nsrs", filter:
390 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
391 :param: progress_timeout: Progress timeout
392 :param: total_timeout: Total timeout
393 :param: config: Dictionary with deployment config information.
394 :param: num_units: Number of units to deploy of a particular charm.
395 :param: vca_id: VCA ID
aktasfa02f8a2021-07-29 17:41:40 +0300396 :param: scaling_out: Boolean to indicate if it is a scaling out operation
397 :param: vca_type: VCA type
David Garciaeb8943a2021-04-12 12:07:37 +0200398 """
quilesj29114342019-10-29 09:30:44 +0100399
beierlmf52cb7c2020-04-21 16:36:35 -0400400 self.log.info(
401 (
402 "Installing configuration sw on ee_id: {}, "
403 "artifact path: {}, db_dict: {}"
404 ).format(ee_id, artifact_path, db_dict)
405 )
David Garciaeb8943a2021-04-12 12:07:37 +0200406 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100407
quilesj29114342019-10-29 09:30:44 +0100408 # check arguments
409 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400410 raise N2VCBadArgumentsException(
411 message="ee_id is mandatory", bad_args=["ee_id"]
412 )
quilesj29114342019-10-29 09:30:44 +0100413 if artifact_path is None or len(artifact_path) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400414 raise N2VCBadArgumentsException(
415 message="artifact_path is mandatory", bad_args=["artifact_path"]
416 )
quilesj29114342019-10-29 09:30:44 +0100417 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400418 raise N2VCBadArgumentsException(
419 message="db_dict is mandatory", bad_args=["db_dict"]
420 )
quilesj29114342019-10-29 09:30:44 +0100421
422 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400423 (
424 model_name,
425 application_name,
426 machine_id,
427 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
428 self.log.debug(
429 "model: {}, application: {}, machine: {}".format(
430 model_name, application_name, machine_id
431 )
432 )
433 except Exception:
quilesj29114342019-10-29 09:30:44 +0100434 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400435 message="ee_id={} is not a valid execution environment id".format(
436 ee_id
437 ),
438 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100439 )
440
441 # remove // in charm path
beierlmf52cb7c2020-04-21 16:36:35 -0400442 while artifact_path.find("//") >= 0:
443 artifact_path = artifact_path.replace("//", "/")
quilesj29114342019-10-29 09:30:44 +0100444
445 # check charm path
David Garcia7114f652021-10-26 17:24:21 +0200446 if not self.fs.file_exists(artifact_path):
beierlmf52cb7c2020-04-21 16:36:35 -0400447 msg = "artifact path does not exist: {}".format(artifact_path)
448 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
quilesj29114342019-10-29 09:30:44 +0100449
beierlmf52cb7c2020-04-21 16:36:35 -0400450 if artifact_path.startswith("/"):
quilesj29114342019-10-29 09:30:44 +0100451 full_path = self.fs.path + artifact_path
452 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400453 full_path = self.fs.path + "/" + artifact_path
quilesj29114342019-10-29 09:30:44 +0100454
455 try:
aktasfa02f8a2021-07-29 17:41:40 +0300456 if vca_type == "native_charm" and await libjuju.check_application_exists(
457 model_name, application_name
458 ):
459 await libjuju.add_unit(
460 application_name=application_name,
461 model_name=model_name,
462 machine_id=machine_id,
463 db_dict=db_dict,
464 progress_timeout=progress_timeout,
465 total_timeout=total_timeout,
466 )
467 else:
468 await libjuju.deploy_charm(
469 model_name=model_name,
470 application_name=application_name,
471 path=full_path,
472 machine_id=machine_id,
473 db_dict=db_dict,
474 progress_timeout=progress_timeout,
475 total_timeout=total_timeout,
476 config=config,
477 num_units=num_units,
478 )
479 except JujuApplicationExists as e:
480 raise N2VCApplicationExists(
481 message="Error deploying charm into ee={} : {}".format(ee_id, e.message)
quilesj29114342019-10-29 09:30:44 +0100482 )
483 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400484 raise N2VCException(
aktasfa02f8a2021-07-29 17:41:40 +0300485 message="Error deploying charm into ee={} : {}".format(ee_id, e)
beierlmf52cb7c2020-04-21 16:36:35 -0400486 )
quilesj29114342019-10-29 09:30:44 +0100487
beierlmf52cb7c2020-04-21 16:36:35 -0400488 self.log.info("Configuration sw installed")
quilesj29114342019-10-29 09:30:44 +0100489
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200490 async def install_k8s_proxy_charm(
491 self,
492 charm_name: str,
493 namespace: str,
494 artifact_path: str,
495 db_dict: dict,
496 progress_timeout: float = None,
497 total_timeout: float = None,
498 config: dict = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200499 vca_id: str = None,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200500 ) -> str:
501 """
502 Install a k8s proxy charm
503
504 :param charm_name: Name of the charm being deployed
505 :param namespace: collection of all the uuids related to the charm.
506 :param str artifact_path: where to locate the artifacts (parent folder) using
507 the self.fs
508 the final artifact path will be a combination of this artifact_path and
509 additional string from the config_dict (e.g. charm name)
510 :param dict db_dict: where to write into database when the status changes.
511 It contains a dict with
512 {collection: <str>, filter: {}, path: <str>},
513 e.g. {collection: "nsrs", filter:
514 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
David Garciaeb8943a2021-04-12 12:07:37 +0200515 :param: progress_timeout: Progress timeout
516 :param: total_timeout: Total timeout
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200517 :param config: Dictionary with additional configuration
David Garciaeb8943a2021-04-12 12:07:37 +0200518 :param vca_id: VCA ID
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200519
520 :returns ee_id: execution environment id.
521 """
David Garciaeb8943a2021-04-12 12:07:37 +0200522 self.log.info(
523 "Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}".format(
524 charm_name, artifact_path, db_dict
525 )
526 )
527 libjuju = await self._get_libjuju(vca_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200528
529 if artifact_path is None or len(artifact_path) == 0:
530 raise N2VCBadArgumentsException(
531 message="artifact_path is mandatory", bad_args=["artifact_path"]
532 )
533 if db_dict is None:
David Garciaeb8943a2021-04-12 12:07:37 +0200534 raise N2VCBadArgumentsException(
535 message="db_dict is mandatory", bad_args=["db_dict"]
536 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200537
538 # remove // in charm path
David Garciaeb8943a2021-04-12 12:07:37 +0200539 while artifact_path.find("//") >= 0:
540 artifact_path = artifact_path.replace("//", "/")
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200541
542 # check charm path
David Garcia7114f652021-10-26 17:24:21 +0200543 if not self.fs.file_exists(artifact_path):
David Garciaeb8943a2021-04-12 12:07:37 +0200544 msg = "artifact path does not exist: {}".format(artifact_path)
545 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200546
David Garciaeb8943a2021-04-12 12:07:37 +0200547 if artifact_path.startswith("/"):
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200548 full_path = self.fs.path + artifact_path
549 else:
David Garciaeb8943a2021-04-12 12:07:37 +0200550 full_path = self.fs.path + "/" + artifact_path
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200551
552 _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace)
David Garciaeb8943a2021-04-12 12:07:37 +0200553 model_name = "{}-k8s".format(ns_id)
554 if not await libjuju.model_exists(model_name):
Patricia Reinoso085942e2022-12-05 16:55:51 +0000555 await libjuju.add_model(model_name, libjuju.vca_connection.k8s_cloud)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200556 application_name = self._get_application_name(namespace)
557
558 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200559 await libjuju.deploy_charm(
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200560 model_name=model_name,
561 application_name=application_name,
562 path=full_path,
563 machine_id=None,
564 db_dict=db_dict,
565 progress_timeout=progress_timeout,
566 total_timeout=total_timeout,
David Garciaeb8943a2021-04-12 12:07:37 +0200567 config=config,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200568 )
569 except Exception as e:
David Garciaeb8943a2021-04-12 12:07:37 +0200570 raise N2VCException(message="Error deploying charm: {}".format(e))
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200571
David Garciaeb8943a2021-04-12 12:07:37 +0200572 self.log.info("K8s proxy charm installed")
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200573 ee_id = N2VCJujuConnector._build_ee_id(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000574 model_name=model_name, application_name=application_name, machine_id="k8s"
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200575 )
Dominik Fleischmann7ace6fa2020-06-29 16:16:28 +0200576
577 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
578
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200579 return ee_id
580
quilesj29114342019-10-29 09:30:44 +0100581 async def get_ee_ssh_public__key(
582 self,
583 ee_id: str,
584 db_dict: dict,
585 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400586 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200587 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100588 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200589 """
590 Get Execution environment ssh public key
591
592 :param: ee_id: the id of the execution environment returned by
593 create_execution_environment or register_execution_environment
594 :param: db_dict: where to write into database when the status changes.
595 It contains a dict with
596 {collection: <str>, filter: {}, path: <str>},
597 e.g. {collection: "nsrs", filter:
598 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
599 :param: progress_timeout: Progress timeout
600 :param: total_timeout: Total timeout
601 :param vca_id: VCA ID
602 :returns: public key of the execution environment
603 For the case of juju proxy charm ssh-layered, it is the one
604 returned by 'get-ssh-public-key' primitive.
605 It raises a N2VC exception if fails
606 """
quilesj29114342019-10-29 09:30:44 +0100607
beierlmf52cb7c2020-04-21 16:36:35 -0400608 self.log.info(
609 (
610 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
611 ).format(ee_id, db_dict)
612 )
David Garciaeb8943a2021-04-12 12:07:37 +0200613 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100614
quilesj29114342019-10-29 09:30:44 +0100615 # check arguments
616 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400617 raise N2VCBadArgumentsException(
618 message="ee_id is mandatory", bad_args=["ee_id"]
619 )
quilesj29114342019-10-29 09:30:44 +0100620 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400621 raise N2VCBadArgumentsException(
622 message="db_dict is mandatory", bad_args=["db_dict"]
623 )
quilesj29114342019-10-29 09:30:44 +0100624
625 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400626 (
627 model_name,
628 application_name,
629 machine_id,
630 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
631 self.log.debug(
632 "model: {}, application: {}, machine: {}".format(
633 model_name, application_name, machine_id
634 )
635 )
636 except Exception:
quilesj29114342019-10-29 09:30:44 +0100637 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400638 message="ee_id={} is not a valid execution environment id".format(
639 ee_id
640 ),
641 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100642 )
643
644 # try to execute ssh layer primitives (if exist):
645 # generate-ssh-key
646 # get-ssh-public-key
647
648 output = None
649
David Garcia4fee80e2020-05-13 12:18:38 +0200650 application_name = N2VCJujuConnector._format_app_name(application_name)
651
quilesj29114342019-10-29 09:30:44 +0100652 # execute action: generate-ssh-key
653 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200654 output, _status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100655 model_name=model_name,
656 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400657 action_name="generate-ssh-key",
quilesj29114342019-10-29 09:30:44 +0100658 db_dict=db_dict,
659 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400660 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100661 )
662 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400663 self.log.info(
664 "Skipping exception while executing action generate-ssh-key: {}".format(
665 e
666 )
667 )
quilesj29114342019-10-29 09:30:44 +0100668
669 # execute action: get-ssh-public-key
670 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200671 output, _status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100672 model_name=model_name,
673 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400674 action_name="get-ssh-public-key",
quilesj29114342019-10-29 09:30:44 +0100675 db_dict=db_dict,
676 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400677 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100678 )
679 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400680 msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100681 self.log.info(msg)
David Garcia4fee80e2020-05-13 12:18:38 +0200682 raise N2VCExecutionException(e, primitive_name="get-ssh-public-key")
quilesj29114342019-10-29 09:30:44 +0100683
684 # return public key if exists
David Garcia9ae8fa52019-12-09 18:50:03 +0100685 return output["pubkey"] if "pubkey" in output else output
quilesj29114342019-10-29 09:30:44 +0100686
David Garciaeb8943a2021-04-12 12:07:37 +0200687 async def get_metrics(
688 self, model_name: str, application_name: str, vca_id: str = None
689 ) -> dict:
690 """
691 Get metrics from application
692
693 :param: model_name: Model name
694 :param: application_name: Application name
695 :param: vca_id: VCA ID
696
697 :return: Dictionary with obtained metrics
698 """
699 libjuju = await self._get_libjuju(vca_id)
700 return await libjuju.get_metrics(model_name, application_name)
David Garcia85755d12020-09-21 19:51:23 +0200701
quilesj29114342019-10-29 09:30:44 +0100702 async def add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000703 self, provider: RelationEndpoint, requirer: RelationEndpoint
quilesj29114342019-10-29 09:30:44 +0100704 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200705 """
706 Add relation between two charmed endpoints
quilesj29114342019-10-29 09:30:44 +0100707
David Garcia582b9232021-10-26 12:30:44 +0200708 :param: provider: Provider relation endpoint
709 :param: requirer: Requirer relation endpoint
David Garciaeb8943a2021-04-12 12:07:37 +0200710 """
David Garcia582b9232021-10-26 12:30:44 +0200711 self.log.debug(f"adding new relation between {provider} and {requirer}")
712 cross_model_relation = (
713 provider.model_name != requirer.model_name
Patricia Reinoso085942e2022-12-05 16:55:51 +0000714 or provider.vca_id != requirer.vca_id
beierlmf52cb7c2020-04-21 16:36:35 -0400715 )
quilesj29114342019-10-29 09:30:44 +0100716 try:
David Garcia582b9232021-10-26 12:30:44 +0200717 if cross_model_relation:
718 # Cross-model relation
719 provider_libjuju = await self._get_libjuju(provider.vca_id)
720 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
721 offer = await provider_libjuju.offer(provider)
722 if offer:
723 saas_name = await requirer_libjuju.consume(
724 requirer.model_name, offer, provider_libjuju
725 )
726 await requirer_libjuju.add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000727 requirer.model_name, requirer.endpoint, saas_name
David Garcia582b9232021-10-26 12:30:44 +0200728 )
729 else:
730 # Standard relation
731 vca_id = provider.vca_id
732 model = provider.model_name
733 libjuju = await self._get_libjuju(vca_id)
734 # add juju relations between two applications
735 await libjuju.add_relation(
736 model_name=model,
737 endpoint_1=provider.endpoint,
738 endpoint_2=requirer.endpoint,
739 )
quilesj29114342019-10-29 09:30:44 +0100740 except Exception as e:
David Garcia582b9232021-10-26 12:30:44 +0200741 message = f"Error adding relation between {provider} and {requirer}: {e}"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100742 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100743 raise N2VCException(message=message)
744
beierlmf52cb7c2020-04-21 16:36:35 -0400745 async def remove_relation(self):
quilesj29114342019-10-29 09:30:44 +0100746 # TODO
beierlmf52cb7c2020-04-21 16:36:35 -0400747 self.log.info("Method not implemented yet")
748 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100749
beierlmf52cb7c2020-04-21 16:36:35 -0400750 async def deregister_execution_environments(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400751 self.log.info("Method not implemented yet")
752 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100753
754 async def delete_namespace(
David Garciaeb8943a2021-04-12 12:07:37 +0200755 self,
756 namespace: str,
757 db_dict: dict = None,
758 total_timeout: float = None,
759 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100760 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200761 """
762 Remove a network scenario and its execution environments
763 :param: namespace: [<nsi-id>].<ns-id>
764 :param: db_dict: where to write into database when the status changes.
765 It contains a dict with
766 {collection: <str>, filter: {}, path: <str>},
767 e.g. {collection: "nsrs", filter:
768 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
769 :param: total_timeout: Total timeout
770 :param: vca_id: VCA ID
771 """
beierlmf52cb7c2020-04-21 16:36:35 -0400772 self.log.info("Deleting namespace={}".format(namespace))
David Garcia1423ffa2022-05-04 15:33:03 +0200773 will_not_delete = False
774 if namespace not in self.delete_namespace_locks:
775 self.delete_namespace_locks[namespace] = asyncio.Lock(loop=self.loop)
David Garciacd986062022-05-05 09:46:06 +0200776 delete_lock = self.delete_namespace_locks[namespace]
quilesj29114342019-10-29 09:30:44 +0100777
David Garciacd986062022-05-05 09:46:06 +0200778 while delete_lock.locked():
David Garcia1423ffa2022-05-04 15:33:03 +0200779 will_not_delete = True
780 await asyncio.sleep(0.1)
quilesj29114342019-10-29 09:30:44 +0100781
David Garcia1423ffa2022-05-04 15:33:03 +0200782 if will_not_delete:
783 self.log.info("Namespace {} deleted by another worker.".format(namespace))
784 return
785
786 try:
David Garciacd986062022-05-05 09:46:06 +0200787 async with delete_lock:
David Garcia1423ffa2022-05-04 15:33:03 +0200788 libjuju = await self._get_libjuju(vca_id)
789
790 # check arguments
791 if namespace is None:
792 raise N2VCBadArgumentsException(
793 message="namespace is mandatory", bad_args=["namespace"]
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200794 )
quilesj29114342019-10-29 09:30:44 +0100795
David Garcia1423ffa2022-05-04 15:33:03 +0200796 (
797 _nsi_id,
798 ns_id,
799 _vnf_id,
800 _vdu_id,
801 _vdu_count,
802 ) = self._get_namespace_components(namespace=namespace)
803 if ns_id is not None:
804 try:
805 models = await libjuju.list_models(contains=ns_id)
806 for model in models:
807 await libjuju.destroy_model(
808 model_name=model, total_timeout=total_timeout
809 )
810 except Exception as e:
David Garcia1608b562022-05-06 12:26:20 +0200811 self.log.error(f"Error deleting namespace {namespace} : {e}")
David Garcia1423ffa2022-05-04 15:33:03 +0200812 raise N2VCException(
813 message="Error deleting namespace {} : {}".format(
814 namespace, e
815 )
816 )
817 else:
818 raise N2VCBadArgumentsException(
819 message="only ns_id is permitted to delete yet",
820 bad_args=["namespace"],
821 )
David Garcia1608b562022-05-06 12:26:20 +0200822 except Exception as e:
823 self.log.error(f"Error deleting namespace {namespace} : {e}")
824 raise e
David Garcia1423ffa2022-05-04 15:33:03 +0200825 finally:
826 self.delete_namespace_locks.pop(namespace)
beierlmf52cb7c2020-04-21 16:36:35 -0400827 self.log.info("Namespace {} deleted".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100828
829 async def delete_execution_environment(
David Garciaeb8943a2021-04-12 12:07:37 +0200830 self,
831 ee_id: str,
832 db_dict: dict = None,
833 total_timeout: float = None,
834 scaling_in: bool = False,
aktasfa02f8a2021-07-29 17:41:40 +0300835 vca_type: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200836 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100837 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200838 """
839 Delete an execution environment
840 :param str ee_id: id of the execution environment to delete
841 :param dict db_dict: where to write into database when the status changes.
842 It contains a dict with
843 {collection: <str>, filter: {}, path: <str>},
844 e.g. {collection: "nsrs", filter:
845 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
846 :param: total_timeout: Total timeout
aktasfa02f8a2021-07-29 17:41:40 +0300847 :param: scaling_in: Boolean to indicate if it is a scaling in operation
848 :param: vca_type: VCA type
David Garciaeb8943a2021-04-12 12:07:37 +0200849 :param: vca_id: VCA ID
850 """
beierlmf52cb7c2020-04-21 16:36:35 -0400851 self.log.info("Deleting execution environment ee_id={}".format(ee_id))
David Garciaeb8943a2021-04-12 12:07:37 +0200852 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100853
quilesj29114342019-10-29 09:30:44 +0100854 # check arguments
855 if ee_id is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400856 raise N2VCBadArgumentsException(
857 message="ee_id is mandatory", bad_args=["ee_id"]
858 )
quilesj29114342019-10-29 09:30:44 +0100859
aktasfa02f8a2021-07-29 17:41:40 +0300860 model_name, application_name, machine_id = self._get_ee_id_components(
beierlmf52cb7c2020-04-21 16:36:35 -0400861 ee_id=ee_id
862 )
quilesj29114342019-10-29 09:30:44 +0100863 try:
aktasd1d55412021-02-21 19:36:20 +0300864 if not scaling_in:
865 # destroy the model
David Garciaeb8943a2021-04-12 12:07:37 +0200866 await libjuju.destroy_model(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000867 model_name=model_name, total_timeout=total_timeout
aktasd1d55412021-02-21 19:36:20 +0300868 )
aktasfa02f8a2021-07-29 17:41:40 +0300869 elif vca_type == "native_charm" and scaling_in:
870 # destroy the unit in the application
871 await libjuju.destroy_unit(
872 application_name=application_name,
873 model_name=model_name,
874 machine_id=machine_id,
aktasfa02f8a2021-07-29 17:41:40 +0300875 total_timeout=total_timeout,
876 )
aktasd1d55412021-02-21 19:36:20 +0300877 else:
aktasd1d55412021-02-21 19:36:20 +0300878 # destroy the application
David Garciaeb8943a2021-04-12 12:07:37 +0200879 await libjuju.destroy_application(
aktas56120292021-02-26 15:32:39 +0300880 model_name=model_name,
881 application_name=application_name,
882 total_timeout=total_timeout,
883 )
quilesj29114342019-10-29 09:30:44 +0100884 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400885 raise N2VCException(
886 message=(
887 "Error deleting execution environment {} (application {}) : {}"
888 ).format(ee_id, application_name, e)
889 )
quilesj29114342019-10-29 09:30:44 +0100890
beierlmf52cb7c2020-04-21 16:36:35 -0400891 self.log.info("Execution environment {} deleted".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100892
893 async def exec_primitive(
beierlmf52cb7c2020-04-21 16:36:35 -0400894 self,
895 ee_id: str,
896 primitive_name: str,
897 params_dict: dict,
898 db_dict: dict = None,
899 progress_timeout: float = None,
900 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200901 vca_id: str = None,
aktasfa02f8a2021-07-29 17:41:40 +0300902 vca_type: str = None,
quilesj29114342019-10-29 09:30:44 +0100903 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200904 """
905 Execute a primitive in the execution environment
906
907 :param: ee_id: the one returned by create_execution_environment or
908 register_execution_environment
909 :param: primitive_name: must be one defined in the software. There is one
910 called 'config', where, for the proxy case, the 'credentials' of VM are
911 provided
912 :param: params_dict: parameters of the action
913 :param: db_dict: where to write into database when the status changes.
914 It contains a dict with
915 {collection: <str>, filter: {}, path: <str>},
916 e.g. {collection: "nsrs", filter:
917 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
918 :param: progress_timeout: Progress timeout
919 :param: total_timeout: Total timeout
920 :param: vca_id: VCA ID
aktasfa02f8a2021-07-29 17:41:40 +0300921 :param: vca_type: VCA type
David Garciaeb8943a2021-04-12 12:07:37 +0200922 :returns str: primitive result, if ok. It raises exceptions in case of fail
923 """
quilesj29114342019-10-29 09:30:44 +0100924
beierlmf52cb7c2020-04-21 16:36:35 -0400925 self.log.info(
926 "Executing primitive: {} on ee: {}, params: {}".format(
927 primitive_name, ee_id, params_dict
928 )
929 )
David Garciaeb8943a2021-04-12 12:07:37 +0200930 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100931
quilesj29114342019-10-29 09:30:44 +0100932 # check arguments
933 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400934 raise N2VCBadArgumentsException(
935 message="ee_id is mandatory", bad_args=["ee_id"]
936 )
quilesj29114342019-10-29 09:30:44 +0100937 if primitive_name is None or len(primitive_name) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400938 raise N2VCBadArgumentsException(
939 message="action_name is mandatory", bad_args=["action_name"]
940 )
quilesj29114342019-10-29 09:30:44 +0100941 if params_dict is None:
942 params_dict = dict()
943
944 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400945 (
946 model_name,
947 application_name,
aktasfa02f8a2021-07-29 17:41:40 +0300948 machine_id,
beierlmf52cb7c2020-04-21 16:36:35 -0400949 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
aktasfa02f8a2021-07-29 17:41:40 +0300950 # To run action on the leader unit in libjuju.execute_action function,
951 # machine_id must be set to None if vca_type is not native_charm
952 if vca_type != "native_charm":
953 machine_id = None
quilesj29114342019-10-29 09:30:44 +0100954 except Exception:
955 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400956 message="ee_id={} is not a valid execution environment id".format(
957 ee_id
958 ),
959 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100960 )
961
beierlmf52cb7c2020-04-21 16:36:35 -0400962 if primitive_name == "config":
quilesj29114342019-10-29 09:30:44 +0100963 # Special case: config primitive
964 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200965 await libjuju.configure_application(
quilesj29114342019-10-29 09:30:44 +0100966 model_name=model_name,
967 application_name=application_name,
968 config=params_dict,
quilesj29114342019-10-29 09:30:44 +0100969 )
David Garciaeb8943a2021-04-12 12:07:37 +0200970 actions = await libjuju.get_actions(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000971 application_name=application_name, model_name=model_name
David Garcia4fee80e2020-05-13 12:18:38 +0200972 )
973 self.log.debug(
974 "Application {} has these actions: {}".format(
975 application_name, actions
976 )
977 )
978 if "verify-ssh-credentials" in actions:
979 # execute verify-credentials
980 num_retries = 20
981 retry_timeout = 15.0
982 for _ in range(num_retries):
983 try:
984 self.log.debug("Executing action verify-ssh-credentials...")
David Garciaeb8943a2021-04-12 12:07:37 +0200985 output, ok = await libjuju.execute_action(
David Garcia4fee80e2020-05-13 12:18:38 +0200986 model_name=model_name,
987 application_name=application_name,
988 action_name="verify-ssh-credentials",
989 db_dict=db_dict,
990 progress_timeout=progress_timeout,
991 total_timeout=total_timeout,
992 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200993
994 if ok == "failed":
995 self.log.debug(
996 "Error executing verify-ssh-credentials: {}. Retrying..."
997 )
998 await asyncio.sleep(retry_timeout)
999
1000 continue
David Garcia4fee80e2020-05-13 12:18:38 +02001001 self.log.debug("Result: {}, output: {}".format(ok, output))
1002 break
1003 except asyncio.CancelledError:
1004 raise
David Garcia4fee80e2020-05-13 12:18:38 +02001005 else:
1006 self.log.error(
1007 "Error executing verify-ssh-credentials after {} retries. ".format(
1008 num_retries
1009 )
1010 )
1011 else:
1012 msg = "Action verify-ssh-credentials does not exist in application {}".format(
1013 application_name
1014 )
1015 self.log.debug(msg=msg)
quilesj29114342019-10-29 09:30:44 +01001016 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001017 self.log.error("Error configuring juju application: {}".format(e))
quilesj29114342019-10-29 09:30:44 +01001018 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -04001019 message="Error configuring application into ee={} : {}".format(
1020 ee_id, e
1021 ),
1022 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +01001023 )
beierlmf52cb7c2020-04-21 16:36:35 -04001024 return "CONFIG OK"
quilesj29114342019-10-29 09:30:44 +01001025 else:
1026 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001027 output, status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +01001028 model_name=model_name,
1029 application_name=application_name,
1030 action_name=primitive_name,
1031 db_dict=db_dict,
aktasfa02f8a2021-07-29 17:41:40 +03001032 machine_id=machine_id,
quilesj29114342019-10-29 09:30:44 +01001033 progress_timeout=progress_timeout,
1034 total_timeout=total_timeout,
David Garcia582b9232021-10-26 12:30:44 +02001035 **params_dict,
quilesj29114342019-10-29 09:30:44 +01001036 )
beierlmf52cb7c2020-04-21 16:36:35 -04001037 if status == "completed":
quilesj29114342019-10-29 09:30:44 +01001038 return output
1039 else:
Mark Beierl015abee2022-08-19 15:02:24 -04001040 if "output" in output:
1041 raise Exception(f'{status}: {output["output"]}')
1042 else:
1043 raise Exception(
1044 f"{status}: No further information received from action"
1045 )
1046
quilesj29114342019-10-29 09:30:44 +01001047 except Exception as e:
Mark Beierl015abee2022-08-19 15:02:24 -04001048 self.log.error(f"Error executing primitive {primitive_name}: {e}")
quilesj29114342019-10-29 09:30:44 +01001049 raise N2VCExecutionException(
Mark Beierl015abee2022-08-19 15:02:24 -04001050 message=f"Error executing primitive {primitive_name} in ee={ee_id}: {e}",
beierlmf52cb7c2020-04-21 16:36:35 -04001051 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +01001052 )
1053
aticig8070c3c2022-04-18 00:31:42 +03001054 async def upgrade_charm(
1055 self,
1056 ee_id: str = None,
1057 path: str = None,
1058 charm_id: str = None,
1059 charm_type: str = None,
1060 timeout: float = None,
1061 ) -> str:
1062 """This method upgrade charms in VNFs
1063
1064 Args:
1065 ee_id: Execution environment id
1066 path: Local path to the charm
1067 charm_id: charm-id
1068 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
1069 timeout: (Float) Timeout for the ns update operation
1070
1071 Returns:
1072 The output of the update operation if status equals to "completed"
1073
1074 """
1075 self.log.info("Upgrading charm: {} on ee: {}".format(path, ee_id))
1076 libjuju = await self._get_libjuju(charm_id)
1077
1078 # check arguments
1079 if ee_id is None or len(ee_id) == 0:
1080 raise N2VCBadArgumentsException(
1081 message="ee_id is mandatory", bad_args=["ee_id"]
1082 )
1083 try:
1084 (
1085 model_name,
1086 application_name,
1087 machine_id,
1088 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
1089
1090 except Exception:
1091 raise N2VCBadArgumentsException(
1092 message="ee_id={} is not a valid execution environment id".format(
1093 ee_id
1094 ),
1095 bad_args=["ee_id"],
1096 )
1097
1098 try:
aticig8070c3c2022-04-18 00:31:42 +03001099 await libjuju.upgrade_charm(
1100 application_name=application_name,
1101 path=path,
1102 model_name=model_name,
1103 total_timeout=timeout,
1104 )
1105
1106 return f"Charm upgraded with application name {application_name}"
1107
1108 except Exception as e:
1109 self.log.error("Error upgrading charm {}: {}".format(path, e))
1110
1111 raise N2VCException(
1112 message="Error upgrading charm {} in ee={} : {}".format(path, ee_id, e)
1113 )
1114
David Garciaeb8943a2021-04-12 12:07:37 +02001115 async def disconnect(self, vca_id: str = None):
1116 """
1117 Disconnect from VCA
1118
1119 :param: vca_id: VCA ID
1120 """
beierlmf52cb7c2020-04-21 16:36:35 -04001121 self.log.info("closing juju N2VC...")
David Garciaeb8943a2021-04-12 12:07:37 +02001122 libjuju = await self._get_libjuju(vca_id)
David Garcia4fee80e2020-05-13 12:18:38 +02001123 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001124 await libjuju.disconnect()
David Garcia4fee80e2020-05-13 12:18:38 +02001125 except Exception as e:
1126 raise N2VCConnectionException(
David Garciaeb8943a2021-04-12 12:07:37 +02001127 message="Error disconnecting controller: {}".format(e),
1128 url=libjuju.vca_connection.data.endpoints,
David Garcia4fee80e2020-05-13 12:18:38 +02001129 )
quilesj29114342019-10-29 09:30:44 +01001130
1131 """
David Garcia4fee80e2020-05-13 12:18:38 +02001132####################################################################################
1133################################### P R I V A T E ##################################
1134####################################################################################
quilesj29114342019-10-29 09:30:44 +01001135 """
1136
David Garciaeb8943a2021-04-12 12:07:37 +02001137 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
1138 """
1139 Get libjuju object
1140
1141 :param: vca_id: VCA ID
1142 If None, get a libjuju object with a Connection to the default VCA
1143 Else, geta libjuju object with a Connection to the specified VCA
1144 """
1145 if not vca_id:
1146 while self.loading_libjuju.locked():
1147 await asyncio.sleep(0.1)
1148 if not self.libjuju:
1149 async with self.loading_libjuju:
1150 vca_connection = await get_connection(self._store)
1151 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
1152 return self.libjuju
1153 else:
1154 vca_connection = await get_connection(self._store, vca_id)
Patricia Reinoso085942e2022-12-05 16:55:51 +00001155 return Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self)
David Garciaeb8943a2021-04-12 12:07:37 +02001156
beierlmf52cb7c2020-04-21 16:36:35 -04001157 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +01001158 # write ee_id to database: _admin.deployed.VCA.x
1159 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001160 the_table = db_dict["collection"]
1161 the_filter = db_dict["filter"]
1162 the_path = db_dict["path"]
1163 if not the_path[-1] == ".":
1164 the_path = the_path + "."
1165 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001166 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +01001167 self.db.set_one(
1168 table=the_table,
1169 q_filter=the_filter,
1170 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -04001171 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +01001172 )
tierno8ff11992020-03-26 09:51:11 +00001173 except asyncio.CancelledError:
1174 raise
quilesj29114342019-10-29 09:30:44 +01001175 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001176 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +01001177
1178 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001179 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +01001180 """
1181 Build an execution environment id form model, application and machine
1182 :param model_name:
1183 :param application_name:
1184 :param machine_id:
1185 :return:
1186 """
1187 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -04001188 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +01001189
1190 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001191 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +01001192 """
1193 Get model, application and machine components from an execution environment id
1194 :param ee_id:
1195 :return: model_name, application_name, machine_id
1196 """
1197
David Garcia582b9232021-10-26 12:30:44 +02001198 return get_ee_id_components(ee_id)
quilesj29114342019-10-29 09:30:44 +01001199
aticig01244642022-07-28 01:12:03 +03001200 @staticmethod
1201 def _find_charm_level(vnf_id: str, vdu_id: str) -> str:
1202 """Decides the charm level.
1203 Args:
1204 vnf_id (str): VNF id
1205 vdu_id (str): VDU id
1206
1207 Returns:
1208 charm_level (str): ns-level or vnf-level or vdu-level
quilesj29114342019-10-29 09:30:44 +01001209 """
aticig01244642022-07-28 01:12:03 +03001210 if vdu_id and not vnf_id:
1211 raise N2VCException(message="If vdu-id exists, vnf-id should be provided.")
1212 if vnf_id and vdu_id:
1213 return "vdu-level"
1214 if vnf_id and not vdu_id:
1215 return "vnf-level"
1216 if not vnf_id and not vdu_id:
1217 return "ns-level"
1218
1219 @staticmethod
1220 def _generate_backward_compatible_application_name(
1221 vnf_id: str, vdu_id: str, vdu_count: str
1222 ) -> str:
1223 """Generate backward compatible application name
1224 by limiting the app name to 50 characters.
1225
1226 Args:
1227 vnf_id (str): VNF ID
1228 vdu_id (str): VDU ID
1229 vdu_count (str): vdu-count-index
1230
1231 Returns:
1232 application_name (str): generated application name
1233
quilesj29114342019-10-29 09:30:44 +01001234 """
quilesj29114342019-10-29 09:30:44 +01001235 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001236 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001237 else:
Adam Israel18046072019-12-08 21:44:29 -05001238 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001239 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001240
1241 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001242 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001243 else:
Adam Israel18046072019-12-08 21:44:29 -05001244 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001245 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001246
1247 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001248 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001249 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001250 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001251
Pedro Escaleira0ebadd82022-03-21 17:54:45 +00001252 # Generate a random suffix with 5 characters (the default size used by K8s)
1253 random_suffix = generate_random_alfanum_string(size=5)
1254
1255 application_name = "app-{}{}{}-{}".format(
1256 vnf_id, vdu_id, vdu_count, random_suffix
1257 )
aticig01244642022-07-28 01:12:03 +03001258 return application_name
1259
1260 @staticmethod
Gulsum Atici552a6012022-10-17 14:40:39 +03001261 def _get_vca_record(search_key: str, vca_records: list, vdu_id: str) -> dict:
1262 """Get the correct VCA record dict depending on the search key
1263
1264 Args:
1265 search_key (str): keyword to find the correct VCA record
1266 vca_records (list): All VCA records as list
1267 vdu_id (str): VDU ID
1268
1269 Returns:
1270 vca_record (dict): Dictionary which includes the correct VCA record
1271
1272 """
1273 return next(
1274 filter(lambda record: record[search_key] == vdu_id, vca_records), {}
1275 )
1276
1277 @staticmethod
aticig01244642022-07-28 01:12:03 +03001278 def _generate_application_name(
1279 charm_level: str,
1280 vnfrs: dict,
1281 vca_records: list,
1282 vnf_count: str = None,
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001283 vdu_id: str = None,
aticig01244642022-07-28 01:12:03 +03001284 vdu_count: str = None,
1285 ) -> str:
1286 """Generate application name to make the relevant charm of VDU/KDU
1287 in the VNFD descriptor become clearly visible.
1288 Limiting the app name to 50 characters.
1289
1290 Args:
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001291 charm_level (str): level of charm
1292 vnfrs (dict): vnf record dict
aticig01244642022-07-28 01:12:03 +03001293 vca_records (list): db_nsr["_admin"]["deployed"]["VCA"] as list
1294 vnf_count (str): vnf count index
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001295 vdu_id (str): VDU ID
aticig01244642022-07-28 01:12:03 +03001296 vdu_count (str): vdu count index
1297
1298 Returns:
1299 application_name (str): generated application name
1300
1301 """
1302 application_name = ""
1303 if charm_level == "ns-level":
1304 if len(vca_records) != 1:
1305 raise N2VCException(message="One VCA record is expected.")
1306 # Only one VCA record is expected if it's ns-level charm.
1307 # Shorten the charm name to its first 40 characters.
1308 charm_name = vca_records[0]["charm_name"][:40]
1309 if not charm_name:
1310 raise N2VCException(message="Charm name should be provided.")
1311 application_name = charm_name + "-ns"
1312
1313 elif charm_level == "vnf-level":
1314 if len(vca_records) < 1:
1315 raise N2VCException(message="One or more VCA record is expected.")
1316 # If VNF is scaled, more than one VCA record may be included in vca_records
1317 # but ee_descriptor_id is same.
1318 # Shorten the ee_descriptor_id and member-vnf-index-ref
1319 # to first 12 characters.
1320 application_name = (
1321 vca_records[0]["ee_descriptor_id"][:12]
1322 + "-"
1323 + vnf_count
1324 + "-"
1325 + vnfrs["member-vnf-index-ref"][:12]
1326 + "-vnf"
1327 )
1328 elif charm_level == "vdu-level":
1329 if len(vca_records) < 1:
1330 raise N2VCException(message="One or more VCA record is expected.")
aticig4c856b32022-08-19 19:58:13 +03001331
1332 # Charms are also used for deployments with Helm charts.
1333 # If deployment unit is a Helm chart/KDU,
1334 # vdu_profile_id and vdu_count will be empty string.
aticig4c856b32022-08-19 19:58:13 +03001335 if vdu_count is None:
1336 vdu_count = ""
1337
aticig01244642022-07-28 01:12:03 +03001338 # If vnf/vdu is scaled, more than one VCA record may be included in vca_records
1339 # but ee_descriptor_id is same.
1340 # Shorten the ee_descriptor_id, member-vnf-index-ref and vdu_profile_id
1341 # to first 12 characters.
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001342 if not vdu_id:
1343 raise N2VCException(message="vdu-id should be provided.")
Gulsum Atici552a6012022-10-17 14:40:39 +03001344
1345 vca_record = N2VCJujuConnector._get_vca_record(
1346 "vdu_id", vca_records, vdu_id
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001347 )
Gulsum Atici552a6012022-10-17 14:40:39 +03001348
1349 if not vca_record:
1350 vca_record = N2VCJujuConnector._get_vca_record(
1351 "kdu_name", vca_records, vdu_id
1352 )
1353
aticig01244642022-07-28 01:12:03 +03001354 application_name = (
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001355 vca_record["ee_descriptor_id"][:12]
aticig01244642022-07-28 01:12:03 +03001356 + "-"
1357 + vnf_count
1358 + "-"
1359 + vnfrs["member-vnf-index-ref"][:12]
1360 + "-"
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001361 + vdu_id[:12]
aticig01244642022-07-28 01:12:03 +03001362 + "-"
1363 + vdu_count
1364 + "-vdu"
1365 )
1366
1367 return application_name
1368
1369 def _get_vnf_count_and_record(
1370 self, charm_level: str, vnf_id_and_count: str
1371 ) -> Tuple[str, dict]:
1372 """Get the vnf count and VNF record depend on charm level
1373
1374 Args:
1375 charm_level (str)
1376 vnf_id_and_count (str)
1377
1378 Returns:
1379 (vnf_count (str), db_vnfr(dict)) as Tuple
1380
1381 """
1382 vnf_count = ""
1383 db_vnfr = {}
1384
1385 if charm_level in ("vnf-level", "vdu-level"):
1386 vnf_id = "-".join(vnf_id_and_count.split("-")[:-1])
1387 vnf_count = vnf_id_and_count.split("-")[-1]
1388 db_vnfr = self.db.get_one("vnfrs", {"_id": vnf_id})
1389
1390 # If the charm is ns level, it returns empty vnf_count and db_vnfr
1391 return vnf_count, db_vnfr
1392
1393 @staticmethod
1394 def _get_vca_records(charm_level: str, db_nsr: dict, db_vnfr: dict) -> list:
1395 """Get the VCA records from db_nsr dict
1396
1397 Args:
1398 charm_level (str): level of charm
1399 db_nsr (dict): NS record from database
1400 db_vnfr (dict): VNF record from database
1401
1402 Returns:
1403 vca_records (list): List of VCA record dictionaries
1404
1405 """
1406 vca_records = {}
1407 if charm_level == "ns-level":
1408 vca_records = list(
1409 filter(
1410 lambda vca_record: vca_record["target_element"] == "ns",
1411 db_nsr["_admin"]["deployed"]["VCA"],
1412 )
1413 )
1414 elif charm_level in ["vnf-level", "vdu-level"]:
1415 vca_records = list(
1416 filter(
1417 lambda vca_record: vca_record["member-vnf-index"]
1418 == db_vnfr["member-vnf-index-ref"],
1419 db_nsr["_admin"]["deployed"]["VCA"],
1420 )
1421 )
1422
1423 return vca_records
1424
1425 def _get_application_name(self, namespace: str) -> str:
1426 """Build application name from namespace
1427
1428 Application name structure:
1429 NS level: <charm-name>-ns
1430 VNF level: <ee-name>-z<vnf-ordinal-scale-number>-<vnf-profile-id>-vnf
1431 VDU level: <ee-name>-z<vnf-ordinal-scale-number>-<vnf-profile-id>-
1432 <vdu-profile-id>-z<vdu-ordinal-scale-number>-vdu
1433
1434 Application naming for backward compatibility (old structure):
1435 NS level: app-<random_value>
1436 VNF level: app-vnf-<vnf-id>-z<ordinal-scale-number>-<random_value>
1437 VDU level: app-vnf-<vnf-id>-z<vnf-ordinal-scale-number>-vdu-
1438 <vdu-id>-cnt-<vdu-count>-z<vdu-ordinal-scale-number>-<random_value>
1439
1440 Args:
1441 namespace (str)
1442
1443 Returns:
1444 application_name (str)
1445
1446 """
1447 # split namespace components
1448 (
1449 nsi_id,
1450 ns_id,
1451 vnf_id_and_count,
1452 vdu_id,
1453 vdu_count,
1454 ) = self._get_namespace_components(namespace=namespace)
1455
1456 if not ns_id:
1457 raise N2VCException(message="ns-id should be provided.")
1458
1459 charm_level = self._find_charm_level(vnf_id_and_count, vdu_id)
1460 db_nsr = self.db.get_one("nsrs", {"_id": ns_id})
1461 vnf_count, db_vnfr = self._get_vnf_count_and_record(
1462 charm_level, vnf_id_and_count
1463 )
1464 vca_records = self._get_vca_records(charm_level, db_nsr, db_vnfr)
1465
1466 if all("charm_name" in vca_record.keys() for vca_record in vca_records):
1467 application_name = self._generate_application_name(
1468 charm_level,
1469 db_vnfr,
1470 vca_records,
1471 vnf_count=vnf_count,
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001472 vdu_id=vdu_id,
aticig01244642022-07-28 01:12:03 +03001473 vdu_count=vdu_count,
1474 )
1475 else:
1476 application_name = self._generate_backward_compatible_application_name(
1477 vnf_id_and_count, vdu_id, vdu_count
1478 )
quilesj29114342019-10-29 09:30:44 +01001479
1480 return N2VCJujuConnector._format_app_name(application_name)
1481
quilesj29114342019-10-29 09:30:44 +01001482 @staticmethod
1483 def _format_model_name(name: str) -> str:
1484 """Format the name of the model.
1485
1486 Model names may only contain lowercase letters, digits and hyphens
1487 """
1488
beierlmf52cb7c2020-04-21 16:36:35 -04001489 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001490
1491 @staticmethod
1492 def _format_app_name(name: str) -> str:
1493 """Format the name of the application (in order to assure valid application name).
1494
1495 Application names have restrictions (run juju deploy --help):
1496 - contains lowercase letters 'a'-'z'
1497 - contains numbers '0'-'9'
1498 - contains hyphens '-'
1499 - starts with a lowercase letter
1500 - not two or more consecutive hyphens
1501 - after a hyphen, not a group with all numbers
1502 """
1503
1504 def all_numbers(s: str) -> bool:
1505 for c in s:
1506 if not c.isdigit():
1507 return False
1508 return True
1509
beierlmf52cb7c2020-04-21 16:36:35 -04001510 new_name = name.replace("_", "-")
1511 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001512 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001513 while new_name.find("--") >= 0:
1514 new_name = new_name.replace("--", "-")
1515 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001516
1517 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001518 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001519 for i in range(len(groups)):
1520 group = groups[i]
1521 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001522 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001523 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001524 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001525 app_name += group
1526
1527 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001528 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001529
1530 return app_name
David Garciaeb8943a2021-04-12 12:07:37 +02001531
1532 async def validate_vca(self, vca_id: str):
1533 """
1534 Validate a VCA by connecting/disconnecting to/from it
1535
1536 :param: vca_id: VCA ID
1537 """
1538 vca_connection = await get_connection(self._store, vca_id=vca_id)
1539 libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self)
1540 controller = await libjuju.get_controller()
1541 await libjuju.disconnect_controller(controller)