blob: 2c2f6af7e0d086db04690ed26904c9b8f512c46b [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:
1099
1100 await libjuju.upgrade_charm(
1101 application_name=application_name,
1102 path=path,
1103 model_name=model_name,
1104 total_timeout=timeout,
1105 )
1106
1107 return f"Charm upgraded with application name {application_name}"
1108
1109 except Exception as e:
1110 self.log.error("Error upgrading charm {}: {}".format(path, e))
1111
1112 raise N2VCException(
1113 message="Error upgrading charm {} in ee={} : {}".format(path, ee_id, e)
1114 )
1115
David Garciaeb8943a2021-04-12 12:07:37 +02001116 async def disconnect(self, vca_id: str = None):
1117 """
1118 Disconnect from VCA
1119
1120 :param: vca_id: VCA ID
1121 """
beierlmf52cb7c2020-04-21 16:36:35 -04001122 self.log.info("closing juju N2VC...")
David Garciaeb8943a2021-04-12 12:07:37 +02001123 libjuju = await self._get_libjuju(vca_id)
David Garcia4fee80e2020-05-13 12:18:38 +02001124 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001125 await libjuju.disconnect()
David Garcia4fee80e2020-05-13 12:18:38 +02001126 except Exception as e:
1127 raise N2VCConnectionException(
David Garciaeb8943a2021-04-12 12:07:37 +02001128 message="Error disconnecting controller: {}".format(e),
1129 url=libjuju.vca_connection.data.endpoints,
David Garcia4fee80e2020-05-13 12:18:38 +02001130 )
quilesj29114342019-10-29 09:30:44 +01001131
1132 """
David Garcia4fee80e2020-05-13 12:18:38 +02001133####################################################################################
1134################################### P R I V A T E ##################################
1135####################################################################################
quilesj29114342019-10-29 09:30:44 +01001136 """
1137
David Garciaeb8943a2021-04-12 12:07:37 +02001138 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
1139 """
1140 Get libjuju object
1141
1142 :param: vca_id: VCA ID
1143 If None, get a libjuju object with a Connection to the default VCA
1144 Else, geta libjuju object with a Connection to the specified VCA
1145 """
1146 if not vca_id:
1147 while self.loading_libjuju.locked():
1148 await asyncio.sleep(0.1)
1149 if not self.libjuju:
1150 async with self.loading_libjuju:
1151 vca_connection = await get_connection(self._store)
1152 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
1153 return self.libjuju
1154 else:
1155 vca_connection = await get_connection(self._store, vca_id)
Patricia Reinoso085942e2022-12-05 16:55:51 +00001156 return Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self)
David Garciaeb8943a2021-04-12 12:07:37 +02001157
beierlmf52cb7c2020-04-21 16:36:35 -04001158 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +01001159
1160 # write ee_id to database: _admin.deployed.VCA.x
1161 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001162 the_table = db_dict["collection"]
1163 the_filter = db_dict["filter"]
1164 the_path = db_dict["path"]
1165 if not the_path[-1] == ".":
1166 the_path = the_path + "."
1167 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001168 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +01001169 self.db.set_one(
1170 table=the_table,
1171 q_filter=the_filter,
1172 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -04001173 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +01001174 )
tierno8ff11992020-03-26 09:51:11 +00001175 except asyncio.CancelledError:
1176 raise
quilesj29114342019-10-29 09:30:44 +01001177 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001178 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +01001179
1180 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001181 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +01001182 """
1183 Build an execution environment id form model, application and machine
1184 :param model_name:
1185 :param application_name:
1186 :param machine_id:
1187 :return:
1188 """
1189 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -04001190 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +01001191
1192 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001193 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +01001194 """
1195 Get model, application and machine components from an execution environment id
1196 :param ee_id:
1197 :return: model_name, application_name, machine_id
1198 """
1199
David Garcia582b9232021-10-26 12:30:44 +02001200 return get_ee_id_components(ee_id)
quilesj29114342019-10-29 09:30:44 +01001201
aticig01244642022-07-28 01:12:03 +03001202 @staticmethod
1203 def _find_charm_level(vnf_id: str, vdu_id: str) -> str:
1204 """Decides the charm level.
1205 Args:
1206 vnf_id (str): VNF id
1207 vdu_id (str): VDU id
1208
1209 Returns:
1210 charm_level (str): ns-level or vnf-level or vdu-level
quilesj29114342019-10-29 09:30:44 +01001211 """
aticig01244642022-07-28 01:12:03 +03001212 if vdu_id and not vnf_id:
1213 raise N2VCException(message="If vdu-id exists, vnf-id should be provided.")
1214 if vnf_id and vdu_id:
1215 return "vdu-level"
1216 if vnf_id and not vdu_id:
1217 return "vnf-level"
1218 if not vnf_id and not vdu_id:
1219 return "ns-level"
1220
1221 @staticmethod
1222 def _generate_backward_compatible_application_name(
1223 vnf_id: str, vdu_id: str, vdu_count: str
1224 ) -> str:
1225 """Generate backward compatible application name
1226 by limiting the app name to 50 characters.
1227
1228 Args:
1229 vnf_id (str): VNF ID
1230 vdu_id (str): VDU ID
1231 vdu_count (str): vdu-count-index
1232
1233 Returns:
1234 application_name (str): generated application name
1235
quilesj29114342019-10-29 09:30:44 +01001236 """
quilesj29114342019-10-29 09:30:44 +01001237 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001238 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001239 else:
Adam Israel18046072019-12-08 21:44:29 -05001240 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001241 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001242
1243 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001244 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001245 else:
Adam Israel18046072019-12-08 21:44:29 -05001246 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001247 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001248
1249 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001250 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001251 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001252 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001253
Pedro Escaleira0ebadd82022-03-21 17:54:45 +00001254 # Generate a random suffix with 5 characters (the default size used by K8s)
1255 random_suffix = generate_random_alfanum_string(size=5)
1256
1257 application_name = "app-{}{}{}-{}".format(
1258 vnf_id, vdu_id, vdu_count, random_suffix
1259 )
aticig01244642022-07-28 01:12:03 +03001260 return application_name
1261
1262 @staticmethod
Gulsum Atici552a6012022-10-17 14:40:39 +03001263 def _get_vca_record(search_key: str, vca_records: list, vdu_id: str) -> dict:
1264 """Get the correct VCA record dict depending on the search key
1265
1266 Args:
1267 search_key (str): keyword to find the correct VCA record
1268 vca_records (list): All VCA records as list
1269 vdu_id (str): VDU ID
1270
1271 Returns:
1272 vca_record (dict): Dictionary which includes the correct VCA record
1273
1274 """
1275 return next(
1276 filter(lambda record: record[search_key] == vdu_id, vca_records), {}
1277 )
1278
1279 @staticmethod
aticig01244642022-07-28 01:12:03 +03001280 def _generate_application_name(
1281 charm_level: str,
1282 vnfrs: dict,
1283 vca_records: list,
1284 vnf_count: str = None,
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001285 vdu_id: str = None,
aticig01244642022-07-28 01:12:03 +03001286 vdu_count: str = None,
1287 ) -> str:
1288 """Generate application name to make the relevant charm of VDU/KDU
1289 in the VNFD descriptor become clearly visible.
1290 Limiting the app name to 50 characters.
1291
1292 Args:
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001293 charm_level (str): level of charm
1294 vnfrs (dict): vnf record dict
aticig01244642022-07-28 01:12:03 +03001295 vca_records (list): db_nsr["_admin"]["deployed"]["VCA"] as list
1296 vnf_count (str): vnf count index
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001297 vdu_id (str): VDU ID
aticig01244642022-07-28 01:12:03 +03001298 vdu_count (str): vdu count index
1299
1300 Returns:
1301 application_name (str): generated application name
1302
1303 """
1304 application_name = ""
1305 if charm_level == "ns-level":
1306 if len(vca_records) != 1:
1307 raise N2VCException(message="One VCA record is expected.")
1308 # Only one VCA record is expected if it's ns-level charm.
1309 # Shorten the charm name to its first 40 characters.
1310 charm_name = vca_records[0]["charm_name"][:40]
1311 if not charm_name:
1312 raise N2VCException(message="Charm name should be provided.")
1313 application_name = charm_name + "-ns"
1314
1315 elif charm_level == "vnf-level":
1316 if len(vca_records) < 1:
1317 raise N2VCException(message="One or more VCA record is expected.")
1318 # If VNF is scaled, more than one VCA record may be included in vca_records
1319 # but ee_descriptor_id is same.
1320 # Shorten the ee_descriptor_id and member-vnf-index-ref
1321 # to first 12 characters.
1322 application_name = (
1323 vca_records[0]["ee_descriptor_id"][:12]
1324 + "-"
1325 + vnf_count
1326 + "-"
1327 + vnfrs["member-vnf-index-ref"][:12]
1328 + "-vnf"
1329 )
1330 elif charm_level == "vdu-level":
1331 if len(vca_records) < 1:
1332 raise N2VCException(message="One or more VCA record is expected.")
aticig4c856b32022-08-19 19:58:13 +03001333
1334 # Charms are also used for deployments with Helm charts.
1335 # If deployment unit is a Helm chart/KDU,
1336 # vdu_profile_id and vdu_count will be empty string.
aticig4c856b32022-08-19 19:58:13 +03001337 if vdu_count is None:
1338 vdu_count = ""
1339
aticig01244642022-07-28 01:12:03 +03001340 # If vnf/vdu is scaled, more than one VCA record may be included in vca_records
1341 # but ee_descriptor_id is same.
1342 # Shorten the ee_descriptor_id, member-vnf-index-ref and vdu_profile_id
1343 # to first 12 characters.
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001344 if not vdu_id:
1345 raise N2VCException(message="vdu-id should be provided.")
Gulsum Atici552a6012022-10-17 14:40:39 +03001346
1347 vca_record = N2VCJujuConnector._get_vca_record(
1348 "vdu_id", vca_records, vdu_id
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001349 )
Gulsum Atici552a6012022-10-17 14:40:39 +03001350
1351 if not vca_record:
1352 vca_record = N2VCJujuConnector._get_vca_record(
1353 "kdu_name", vca_records, vdu_id
1354 )
1355
aticig01244642022-07-28 01:12:03 +03001356 application_name = (
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001357 vca_record["ee_descriptor_id"][:12]
aticig01244642022-07-28 01:12:03 +03001358 + "-"
1359 + vnf_count
1360 + "-"
1361 + vnfrs["member-vnf-index-ref"][:12]
1362 + "-"
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001363 + vdu_id[:12]
aticig01244642022-07-28 01:12:03 +03001364 + "-"
1365 + vdu_count
1366 + "-vdu"
1367 )
1368
1369 return application_name
1370
1371 def _get_vnf_count_and_record(
1372 self, charm_level: str, vnf_id_and_count: str
1373 ) -> Tuple[str, dict]:
1374 """Get the vnf count and VNF record depend on charm level
1375
1376 Args:
1377 charm_level (str)
1378 vnf_id_and_count (str)
1379
1380 Returns:
1381 (vnf_count (str), db_vnfr(dict)) as Tuple
1382
1383 """
1384 vnf_count = ""
1385 db_vnfr = {}
1386
1387 if charm_level in ("vnf-level", "vdu-level"):
1388 vnf_id = "-".join(vnf_id_and_count.split("-")[:-1])
1389 vnf_count = vnf_id_and_count.split("-")[-1]
1390 db_vnfr = self.db.get_one("vnfrs", {"_id": vnf_id})
1391
1392 # If the charm is ns level, it returns empty vnf_count and db_vnfr
1393 return vnf_count, db_vnfr
1394
1395 @staticmethod
1396 def _get_vca_records(charm_level: str, db_nsr: dict, db_vnfr: dict) -> list:
1397 """Get the VCA records from db_nsr dict
1398
1399 Args:
1400 charm_level (str): level of charm
1401 db_nsr (dict): NS record from database
1402 db_vnfr (dict): VNF record from database
1403
1404 Returns:
1405 vca_records (list): List of VCA record dictionaries
1406
1407 """
1408 vca_records = {}
1409 if charm_level == "ns-level":
1410 vca_records = list(
1411 filter(
1412 lambda vca_record: vca_record["target_element"] == "ns",
1413 db_nsr["_admin"]["deployed"]["VCA"],
1414 )
1415 )
1416 elif charm_level in ["vnf-level", "vdu-level"]:
1417 vca_records = list(
1418 filter(
1419 lambda vca_record: vca_record["member-vnf-index"]
1420 == db_vnfr["member-vnf-index-ref"],
1421 db_nsr["_admin"]["deployed"]["VCA"],
1422 )
1423 )
1424
1425 return vca_records
1426
1427 def _get_application_name(self, namespace: str) -> str:
1428 """Build application name from namespace
1429
1430 Application name structure:
1431 NS level: <charm-name>-ns
1432 VNF level: <ee-name>-z<vnf-ordinal-scale-number>-<vnf-profile-id>-vnf
1433 VDU level: <ee-name>-z<vnf-ordinal-scale-number>-<vnf-profile-id>-
1434 <vdu-profile-id>-z<vdu-ordinal-scale-number>-vdu
1435
1436 Application naming for backward compatibility (old structure):
1437 NS level: app-<random_value>
1438 VNF level: app-vnf-<vnf-id>-z<ordinal-scale-number>-<random_value>
1439 VDU level: app-vnf-<vnf-id>-z<vnf-ordinal-scale-number>-vdu-
1440 <vdu-id>-cnt-<vdu-count>-z<vdu-ordinal-scale-number>-<random_value>
1441
1442 Args:
1443 namespace (str)
1444
1445 Returns:
1446 application_name (str)
1447
1448 """
1449 # split namespace components
1450 (
1451 nsi_id,
1452 ns_id,
1453 vnf_id_and_count,
1454 vdu_id,
1455 vdu_count,
1456 ) = self._get_namespace_components(namespace=namespace)
1457
1458 if not ns_id:
1459 raise N2VCException(message="ns-id should be provided.")
1460
1461 charm_level = self._find_charm_level(vnf_id_and_count, vdu_id)
1462 db_nsr = self.db.get_one("nsrs", {"_id": ns_id})
1463 vnf_count, db_vnfr = self._get_vnf_count_and_record(
1464 charm_level, vnf_id_and_count
1465 )
1466 vca_records = self._get_vca_records(charm_level, db_nsr, db_vnfr)
1467
1468 if all("charm_name" in vca_record.keys() for vca_record in vca_records):
1469 application_name = self._generate_application_name(
1470 charm_level,
1471 db_vnfr,
1472 vca_records,
1473 vnf_count=vnf_count,
Gulsum Aticid1f257e2022-10-16 21:13:53 +03001474 vdu_id=vdu_id,
aticig01244642022-07-28 01:12:03 +03001475 vdu_count=vdu_count,
1476 )
1477 else:
1478 application_name = self._generate_backward_compatible_application_name(
1479 vnf_id_and_count, vdu_id, vdu_count
1480 )
quilesj29114342019-10-29 09:30:44 +01001481
1482 return N2VCJujuConnector._format_app_name(application_name)
1483
quilesj29114342019-10-29 09:30:44 +01001484 @staticmethod
1485 def _format_model_name(name: str) -> str:
1486 """Format the name of the model.
1487
1488 Model names may only contain lowercase letters, digits and hyphens
1489 """
1490
beierlmf52cb7c2020-04-21 16:36:35 -04001491 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001492
1493 @staticmethod
1494 def _format_app_name(name: str) -> str:
1495 """Format the name of the application (in order to assure valid application name).
1496
1497 Application names have restrictions (run juju deploy --help):
1498 - contains lowercase letters 'a'-'z'
1499 - contains numbers '0'-'9'
1500 - contains hyphens '-'
1501 - starts with a lowercase letter
1502 - not two or more consecutive hyphens
1503 - after a hyphen, not a group with all numbers
1504 """
1505
1506 def all_numbers(s: str) -> bool:
1507 for c in s:
1508 if not c.isdigit():
1509 return False
1510 return True
1511
beierlmf52cb7c2020-04-21 16:36:35 -04001512 new_name = name.replace("_", "-")
1513 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001514 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001515 while new_name.find("--") >= 0:
1516 new_name = new_name.replace("--", "-")
1517 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001518
1519 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001520 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001521 for i in range(len(groups)):
1522 group = groups[i]
1523 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001524 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001525 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001526 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001527 app_name += group
1528
1529 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001530 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001531
1532 return app_name
David Garciaeb8943a2021-04-12 12:07:37 +02001533
1534 async def validate_vca(self, vca_id: str):
1535 """
1536 Validate a VCA by connecting/disconnecting to/from it
1537
1538 :param: vca_id: VCA ID
1539 """
1540 vca_connection = await get_connection(self._store, vca_id=vca_id)
1541 libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self)
1542 controller = await libjuju.get_controller()
1543 await libjuju.disconnect_controller(controller)