blob: eed2f30eafcc794dfb1dba11533576258a151b3e [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
beierlmf52cb7c2020-04-21 16:36:35 -040027from n2vc.exceptions import (
28 N2VCBadArgumentsException,
29 N2VCException,
30 N2VCConnectionException,
31 N2VCExecutionException,
almagiaba6e5322020-09-16 09:44:40 +020032 # N2VCNotFound,
beierlmf52cb7c2020-04-21 16:36:35 -040033 MethodNotImplemented,
34)
quilesj29114342019-10-29 09:30:44 +010035from n2vc.n2vc_conn import N2VCConnector
quilesj776ab392019-12-12 16:10:54 +000036from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
David Garcia4fee80e2020-05-13 12:18:38 +020037from n2vc.libjuju import Libjuju
David Garciaeb8943a2021-04-12 12:07:37 +020038from n2vc.store import MotorStore
39from n2vc.vca.connection import get_connection
quilesj29114342019-10-29 09:30:44 +010040
41
42class N2VCJujuConnector(N2VCConnector):
43
44 """
David Garciaeb8943a2021-04-12 12:07:37 +020045 ####################################################################################
46 ################################### P U B L I C ####################################
47 ####################################################################################
quilesj29114342019-10-29 09:30:44 +010048 """
49
David Garcia347aae62020-04-29 12:34:23 +020050 BUILT_IN_CLOUDS = ["localhost", "microk8s"]
David Garciaeb8943a2021-04-12 12:07:37 +020051 libjuju = None
David Garcia347aae62020-04-29 12:34:23 +020052
quilesj29114342019-10-29 09:30:44 +010053 def __init__(
54 self,
55 db: object,
56 fs: object,
57 log: object = None,
58 loop: object = None,
beierlmf52cb7c2020-04-21 16:36:35 -040059 on_update_db=None,
quilesj29114342019-10-29 09:30:44 +010060 ):
David Garciaeb8943a2021-04-12 12:07:37 +020061 """
62 Constructor
63
64 :param: db: Database object from osm_common
65 :param: fs: Filesystem object from osm_common
66 :param: log: Logger
67 :param: loop: Asyncio loop
68 :param: on_update_db: Callback function to be called for updating the database.
quilesj29114342019-10-29 09:30:44 +010069 """
70
71 # parent class constructor
72 N2VCConnector.__init__(
73 self,
74 db=db,
75 fs=fs,
76 log=log,
77 loop=loop,
beierlmf52cb7c2020-04-21 16:36:35 -040078 on_update_db=on_update_db,
quilesj29114342019-10-29 09:30:44 +010079 )
80
81 # silence websocket traffic log
beierlmf52cb7c2020-04-21 16:36:35 -040082 logging.getLogger("websockets.protocol").setLevel(logging.INFO)
83 logging.getLogger("juju.client.connection").setLevel(logging.WARN)
84 logging.getLogger("model").setLevel(logging.WARN)
quilesj29114342019-10-29 09:30:44 +010085
beierlmf52cb7c2020-04-21 16:36:35 -040086 self.log.info("Initializing N2VC juju connector...")
quilesj29114342019-10-29 09:30:44 +010087
David Garciaeb8943a2021-04-12 12:07:37 +020088 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
89 self._store = MotorStore(db_uri)
90 self.loading_libjuju = asyncio.Lock(loop=self.loop)
quilesj29114342019-10-29 09:30:44 +010091
beierlmf52cb7c2020-04-21 16:36:35 -040092 self.log.info("N2VC juju connector initialized")
quilesj29114342019-10-29 09:30:44 +010093
David Garciaeb8943a2021-04-12 12:07:37 +020094 async def get_status(
95 self, namespace: str, yaml_format: bool = True, vca_id: str = None
96 ):
97 """
98 Get status from all juju models from a VCA
99
100 :param namespace: we obtain ns from namespace
101 :param yaml_format: returns a yaml string
102 :param: vca_id: VCA ID from which the status will be retrieved.
103 """
104 # TODO: Review where is this function used. It is not optimal at all to get the status
105 # from all the juju models of a particular VCA. Additionally, these models might
106 # not have been deployed by OSM, in that case we are getting information from
107 # deployments outside of OSM's scope.
quilesj776ab392019-12-12 16:10:54 +0000108
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100109 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200110 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100111
beierlmf52cb7c2020-04-21 16:36:35 -0400112 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
113 namespace=namespace
114 )
quilesj29114342019-10-29 09:30:44 +0100115 # model name is ns_id
116 model_name = ns_id
117 if model_name is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400118 msg = "Namespace {} not valid".format(namespace)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100119 self.log.error(msg)
beierlmf52cb7c2020-04-21 16:36:35 -0400120 raise N2VCBadArgumentsException(msg, ["namespace"])
quilesj29114342019-10-29 09:30:44 +0100121
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200122 status = {}
David Garciaeb8943a2021-04-12 12:07:37 +0200123 models = await libjuju.list_models(contains=ns_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200124
125 for m in models:
David Garciaeb8943a2021-04-12 12:07:37 +0200126 status[m] = await libjuju.get_model_status(m)
127
quilesj776ab392019-12-12 16:10:54 +0000128 if yaml_format:
129 return obj_to_yaml(status)
130 else:
131 return obj_to_dict(status)
quilesj29114342019-10-29 09:30:44 +0100132
David Garciaeb8943a2021-04-12 12:07:37 +0200133 async def update_vca_status(self, vcastatus: dict, vca_id: str = None):
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530134 """
135 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
David Garciaeb8943a2021-04-12 12:07:37 +0200136
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530137 :param vcastatus: dict containing vcaStatus
David Garciaeb8943a2021-04-12 12:07:37 +0200138 :param: vca_id: VCA ID
139
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530140 :return: None
141 """
142 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200143 libjuju = await self._get_libjuju(vca_id)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530144 for model_name in vcastatus:
145 # Adding executed actions
garciadeblas82b591c2021-03-24 09:22:13 +0100146 vcastatus[model_name][
147 "executedActions"
148 ] = await libjuju.get_executed_actions(model_name)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530149 for application in vcastatus[model_name]["applications"]:
150 # Adding application actions
garciadeblas82b591c2021-03-24 09:22:13 +0100151 vcastatus[model_name]["applications"][application][
152 "actions"
153 ] = await libjuju.get_actions(application, model_name)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530154 # Adding application configs
garciadeblas82b591c2021-03-24 09:22:13 +0100155 vcastatus[model_name]["applications"][application][
156 "configs"
157 ] = await libjuju.get_application_configs(model_name, application)
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530158 except Exception as e:
159 self.log.debug("Error in updating vca status: {}".format(str(e)))
160
quilesj29114342019-10-29 09:30:44 +0100161 async def create_execution_environment(
162 self,
163 namespace: str,
164 db_dict: dict,
165 reuse_ee_id: str = None,
166 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400167 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200168 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100169 ) -> (str, dict):
David Garciaeb8943a2021-04-12 12:07:37 +0200170 """
171 Create an Execution Environment. Returns when it is created or raises an
172 exception on failing
173
174 :param: namespace: Contains a dot separate string.
175 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
176 :param: db_dict: where to write to database when the status changes.
177 It contains a dictionary with {collection: str, filter: {}, path: str},
178 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
179 "_admin.deployed.VCA.3"}
180 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
181 older environment
182 :param: progress_timeout: Progress timeout
183 :param: total_timeout: Total timeout
184 :param: vca_id: VCA ID
185
186 :returns: id of the new execution environment and credentials for it
187 (credentials can contains hostname, username, etc depending on underlying cloud)
188 """
quilesj29114342019-10-29 09:30:44 +0100189
beierlmf52cb7c2020-04-21 16:36:35 -0400190 self.log.info(
191 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
192 namespace, reuse_ee_id
193 )
194 )
David Garciaeb8943a2021-04-12 12:07:37 +0200195 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100196
quilesj29114342019-10-29 09:30:44 +0100197 machine_id = None
198 if reuse_ee_id:
beierlmf52cb7c2020-04-21 16:36:35 -0400199 model_name, application_name, machine_id = self._get_ee_id_components(
200 ee_id=reuse_ee_id
201 )
quilesj29114342019-10-29 09:30:44 +0100202 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400203 (
204 _nsi_id,
205 ns_id,
206 _vnf_id,
207 _vdu_id,
208 _vdu_count,
209 ) = self._get_namespace_components(namespace=namespace)
quilesj29114342019-10-29 09:30:44 +0100210 # model name is ns_id
211 model_name = ns_id
212 # application name
213 application_name = self._get_application_name(namespace=namespace)
214
beierlmf52cb7c2020-04-21 16:36:35 -0400215 self.log.debug(
216 "model name: {}, application name: {}, machine_id: {}".format(
217 model_name, application_name, machine_id
218 )
219 )
quilesj29114342019-10-29 09:30:44 +0100220
221 # create or reuse a new juju machine
222 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200223 if not await libjuju.model_exists(model_name):
224 await libjuju.add_model(
David Garcia9a63e8d2020-11-03 20:37:06 +0100225 model_name,
David Garciaeb8943a2021-04-12 12:07:37 +0200226 libjuju.vca_connection.lxd_cloud,
David Garcia9a63e8d2020-11-03 20:37:06 +0100227 )
David Garciaeb8943a2021-04-12 12:07:37 +0200228 machine, new = await libjuju.create_machine(
quilesj29114342019-10-29 09:30:44 +0100229 model_name=model_name,
quilesj29114342019-10-29 09:30:44 +0100230 machine_id=machine_id,
231 db_dict=db_dict,
232 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400233 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100234 )
David Garcia4fee80e2020-05-13 12:18:38 +0200235 # id for the execution environment
236 ee_id = N2VCJujuConnector._build_ee_id(
237 model_name=model_name,
238 application_name=application_name,
239 machine_id=str(machine.entity_id),
240 )
241 self.log.debug("ee_id: {}".format(ee_id))
242
243 if new:
244 # write ee_id in database
245 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
246
quilesj29114342019-10-29 09:30:44 +0100247 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400248 message = "Error creating machine on juju: {}".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100249 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100250 raise N2VCException(message=message)
251
quilesj29114342019-10-29 09:30:44 +0100252 # new machine credentials
David Garcia4fee80e2020-05-13 12:18:38 +0200253 credentials = {
254 "hostname": machine.dns_name,
255 }
quilesj29114342019-10-29 09:30:44 +0100256
beierlmf52cb7c2020-04-21 16:36:35 -0400257 self.log.info(
258 "Execution environment created. ee_id: {}, credentials: {}".format(
259 ee_id, credentials
260 )
261 )
quilesj29114342019-10-29 09:30:44 +0100262
263 return ee_id, credentials
264
265 async def register_execution_environment(
266 self,
267 namespace: str,
268 credentials: dict,
269 db_dict: dict,
270 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400271 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200272 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100273 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200274 """
275 Register an existing execution environment at the VCA
quilesj29114342019-10-29 09:30:44 +0100276
David Garciaeb8943a2021-04-12 12:07:37 +0200277 :param: namespace: Contains a dot separate string.
278 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
279 :param: credentials: credentials to access the existing execution environment
280 (it can contains hostname, username, path to private key,
281 etc depending on underlying cloud)
282 :param: db_dict: where to write to database when the status changes.
283 It contains a dictionary with {collection: str, filter: {}, path: str},
284 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
285 "_admin.deployed.VCA.3"}
286 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
287 older environment
288 :param: progress_timeout: Progress timeout
289 :param: total_timeout: Total timeout
290 :param: vca_id: VCA ID
291
292 :returns: id of the execution environment
293 """
beierlmf52cb7c2020-04-21 16:36:35 -0400294 self.log.info(
295 "Registering execution environment. namespace={}, credentials={}".format(
296 namespace, credentials
297 )
298 )
David Garciaeb8943a2021-04-12 12:07:37 +0200299 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100300
301 if credentials is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400302 raise N2VCBadArgumentsException(
303 message="credentials are mandatory", bad_args=["credentials"]
304 )
305 if credentials.get("hostname"):
306 hostname = credentials["hostname"]
quilesj29114342019-10-29 09:30:44 +0100307 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400308 raise N2VCBadArgumentsException(
309 message="hostname is mandatory", bad_args=["credentials.hostname"]
310 )
311 if credentials.get("username"):
312 username = credentials["username"]
quilesj29114342019-10-29 09:30:44 +0100313 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400314 raise N2VCBadArgumentsException(
315 message="username is mandatory", bad_args=["credentials.username"]
316 )
317 if "private_key_path" in credentials:
318 private_key_path = credentials["private_key_path"]
quilesj29114342019-10-29 09:30:44 +0100319 else:
320 # if not passed as argument, use generated private key path
321 private_key_path = self.private_key_path
322
beierlmf52cb7c2020-04-21 16:36:35 -0400323 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
324 namespace=namespace
325 )
quilesj29114342019-10-29 09:30:44 +0100326
327 # model name
328 model_name = ns_id
329 # application name
330 application_name = self._get_application_name(namespace=namespace)
331
332 # register machine on juju
333 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200334 if not await libjuju.model_exists(model_name):
335 await libjuju.add_model(
David Garcia9a63e8d2020-11-03 20:37:06 +0100336 model_name,
David Garciaeb8943a2021-04-12 12:07:37 +0200337 libjuju.vca_connection.lxd_cloud,
David Garcia9a63e8d2020-11-03 20:37:06 +0100338 )
David Garciaeb8943a2021-04-12 12:07:37 +0200339 machine_id = await libjuju.provision_machine(
quilesj29114342019-10-29 09:30:44 +0100340 model_name=model_name,
341 hostname=hostname,
342 username=username,
343 private_key_path=private_key_path,
344 db_dict=db_dict,
345 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400346 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100347 )
348 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400349 self.log.error("Error registering machine: {}".format(e))
350 raise N2VCException(
351 message="Error registering machine on juju: {}".format(e)
352 )
quilesjac4e0de2019-11-27 16:12:02 +0000353
beierlmf52cb7c2020-04-21 16:36:35 -0400354 self.log.info("Machine registered: {}".format(machine_id))
quilesj29114342019-10-29 09:30:44 +0100355
356 # id for the execution environment
357 ee_id = N2VCJujuConnector._build_ee_id(
358 model_name=model_name,
359 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400360 machine_id=str(machine_id),
quilesj29114342019-10-29 09:30:44 +0100361 )
362
beierlmf52cb7c2020-04-21 16:36:35 -0400363 self.log.info("Execution environment registered. ee_id: {}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100364
365 return ee_id
366
367 async def install_configuration_sw(
368 self,
369 ee_id: str,
370 artifact_path: str,
371 db_dict: dict,
372 progress_timeout: float = None,
David Garciadfaa6e82020-04-01 16:06:39 +0200373 total_timeout: float = None,
374 config: dict = None,
David Garciaf8a9d462020-03-25 18:19:02 +0100375 num_units: int = 1,
David Garciaeb8943a2021-04-12 12:07:37 +0200376 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100377 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200378 """
379 Install the software inside the execution environment identified by ee_id
380
381 :param: ee_id: the id of the execution environment returned by
382 create_execution_environment or register_execution_environment
383 :param: artifact_path: where to locate the artifacts (parent folder) using
384 the self.fs
385 the final artifact path will be a combination of this
386 artifact_path and additional string from the config_dict
387 (e.g. charm name)
388 :param: db_dict: where to write into database when the status changes.
389 It contains a dict with
390 {collection: <str>, filter: {}, path: <str>},
391 e.g. {collection: "nsrs", filter:
392 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
393 :param: progress_timeout: Progress timeout
394 :param: total_timeout: Total timeout
395 :param: config: Dictionary with deployment config information.
396 :param: num_units: Number of units to deploy of a particular charm.
397 :param: vca_id: VCA ID
398 """
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
446 if not self.fs.file_exists(artifact_path, mode="dir"):
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:
David Garciaeb8943a2021-04-12 12:07:37 +0200456 await libjuju.deploy_charm(
quilesj29114342019-10-29 09:30:44 +0100457 model_name=model_name,
458 application_name=application_name,
David Garcia4fee80e2020-05-13 12:18:38 +0200459 path=full_path,
quilesj29114342019-10-29 09:30:44 +0100460 machine_id=machine_id,
461 db_dict=db_dict,
462 progress_timeout=progress_timeout,
David Garciadfaa6e82020-04-01 16:06:39 +0200463 total_timeout=total_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400464 config=config,
David Garciaf8a9d462020-03-25 18:19:02 +0100465 num_units=num_units,
quilesj29114342019-10-29 09:30:44 +0100466 )
467 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400468 raise N2VCException(
469 message="Error desploying charm into ee={} : {}".format(ee_id, e)
470 )
quilesj29114342019-10-29 09:30:44 +0100471
beierlmf52cb7c2020-04-21 16:36:35 -0400472 self.log.info("Configuration sw installed")
quilesj29114342019-10-29 09:30:44 +0100473
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200474 async def install_k8s_proxy_charm(
475 self,
476 charm_name: str,
477 namespace: str,
478 artifact_path: str,
479 db_dict: dict,
480 progress_timeout: float = None,
481 total_timeout: float = None,
482 config: dict = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200483 vca_id: str = None,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200484 ) -> str:
485 """
486 Install a k8s proxy charm
487
488 :param charm_name: Name of the charm being deployed
489 :param namespace: collection of all the uuids related to the charm.
490 :param str artifact_path: where to locate the artifacts (parent folder) using
491 the self.fs
492 the final artifact path will be a combination of this artifact_path and
493 additional string from the config_dict (e.g. charm name)
494 :param dict db_dict: where to write into database when the status changes.
495 It contains a dict with
496 {collection: <str>, filter: {}, path: <str>},
497 e.g. {collection: "nsrs", filter:
498 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
David Garciaeb8943a2021-04-12 12:07:37 +0200499 :param: progress_timeout: Progress timeout
500 :param: total_timeout: Total timeout
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200501 :param config: Dictionary with additional configuration
David Garciaeb8943a2021-04-12 12:07:37 +0200502 :param vca_id: VCA ID
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200503
504 :returns ee_id: execution environment id.
505 """
David Garciaeb8943a2021-04-12 12:07:37 +0200506 self.log.info(
507 "Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}".format(
508 charm_name, artifact_path, db_dict
509 )
510 )
511 libjuju = await self._get_libjuju(vca_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200512
513 if artifact_path is None or len(artifact_path) == 0:
514 raise N2VCBadArgumentsException(
515 message="artifact_path is mandatory", bad_args=["artifact_path"]
516 )
517 if db_dict is None:
David Garciaeb8943a2021-04-12 12:07:37 +0200518 raise N2VCBadArgumentsException(
519 message="db_dict is mandatory", bad_args=["db_dict"]
520 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200521
522 # remove // in charm path
David Garciaeb8943a2021-04-12 12:07:37 +0200523 while artifact_path.find("//") >= 0:
524 artifact_path = artifact_path.replace("//", "/")
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200525
526 # check charm path
527 if not self.fs.file_exists(artifact_path, mode="dir"):
David Garciaeb8943a2021-04-12 12:07:37 +0200528 msg = "artifact path does not exist: {}".format(artifact_path)
529 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200530
David Garciaeb8943a2021-04-12 12:07:37 +0200531 if artifact_path.startswith("/"):
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200532 full_path = self.fs.path + artifact_path
533 else:
David Garciaeb8943a2021-04-12 12:07:37 +0200534 full_path = self.fs.path + "/" + artifact_path
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200535
536 _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace)
David Garciaeb8943a2021-04-12 12:07:37 +0200537 model_name = "{}-k8s".format(ns_id)
538 if not await libjuju.model_exists(model_name):
539 await libjuju.add_model(
aktasd1d55412021-02-21 19:36:20 +0300540 model_name,
David Garciaeb8943a2021-04-12 12:07:37 +0200541 libjuju.vca_connection.k8s_cloud,
aktasd1d55412021-02-21 19:36:20 +0300542 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200543 application_name = self._get_application_name(namespace)
544
545 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200546 await libjuju.deploy_charm(
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200547 model_name=model_name,
548 application_name=application_name,
549 path=full_path,
550 machine_id=None,
551 db_dict=db_dict,
552 progress_timeout=progress_timeout,
553 total_timeout=total_timeout,
David Garciaeb8943a2021-04-12 12:07:37 +0200554 config=config,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200555 )
556 except Exception as e:
David Garciaeb8943a2021-04-12 12:07:37 +0200557 raise N2VCException(message="Error deploying charm: {}".format(e))
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200558
David Garciaeb8943a2021-04-12 12:07:37 +0200559 self.log.info("K8s proxy charm installed")
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200560 ee_id = N2VCJujuConnector._build_ee_id(
561 model_name=model_name,
562 application_name=application_name,
563 machine_id="k8s",
564 )
Dominik Fleischmann7ace6fa2020-06-29 16:16:28 +0200565
566 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
567
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200568 return ee_id
569
quilesj29114342019-10-29 09:30:44 +0100570 async def get_ee_ssh_public__key(
571 self,
572 ee_id: str,
573 db_dict: dict,
574 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400575 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200576 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100577 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200578 """
579 Get Execution environment ssh public key
580
581 :param: ee_id: the id of the execution environment returned by
582 create_execution_environment or register_execution_environment
583 :param: db_dict: where to write into database when the status changes.
584 It contains a dict with
585 {collection: <str>, filter: {}, path: <str>},
586 e.g. {collection: "nsrs", filter:
587 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
588 :param: progress_timeout: Progress timeout
589 :param: total_timeout: Total timeout
590 :param vca_id: VCA ID
591 :returns: public key of the execution environment
592 For the case of juju proxy charm ssh-layered, it is the one
593 returned by 'get-ssh-public-key' primitive.
594 It raises a N2VC exception if fails
595 """
quilesj29114342019-10-29 09:30:44 +0100596
beierlmf52cb7c2020-04-21 16:36:35 -0400597 self.log.info(
598 (
599 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
600 ).format(ee_id, db_dict)
601 )
David Garciaeb8943a2021-04-12 12:07:37 +0200602 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100603
quilesj29114342019-10-29 09:30:44 +0100604 # check arguments
605 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400606 raise N2VCBadArgumentsException(
607 message="ee_id is mandatory", bad_args=["ee_id"]
608 )
quilesj29114342019-10-29 09:30:44 +0100609 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400610 raise N2VCBadArgumentsException(
611 message="db_dict is mandatory", bad_args=["db_dict"]
612 )
quilesj29114342019-10-29 09:30:44 +0100613
614 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400615 (
616 model_name,
617 application_name,
618 machine_id,
619 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
620 self.log.debug(
621 "model: {}, application: {}, machine: {}".format(
622 model_name, application_name, machine_id
623 )
624 )
625 except Exception:
quilesj29114342019-10-29 09:30:44 +0100626 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400627 message="ee_id={} is not a valid execution environment id".format(
628 ee_id
629 ),
630 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100631 )
632
633 # try to execute ssh layer primitives (if exist):
634 # generate-ssh-key
635 # get-ssh-public-key
636
637 output = None
638
David Garcia4fee80e2020-05-13 12:18:38 +0200639 application_name = N2VCJujuConnector._format_app_name(application_name)
640
quilesj29114342019-10-29 09:30:44 +0100641 # execute action: generate-ssh-key
642 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200643 output, _status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100644 model_name=model_name,
645 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400646 action_name="generate-ssh-key",
quilesj29114342019-10-29 09:30:44 +0100647 db_dict=db_dict,
648 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400649 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100650 )
651 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400652 self.log.info(
653 "Skipping exception while executing action generate-ssh-key: {}".format(
654 e
655 )
656 )
quilesj29114342019-10-29 09:30:44 +0100657
658 # execute action: get-ssh-public-key
659 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200660 output, _status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100661 model_name=model_name,
662 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400663 action_name="get-ssh-public-key",
quilesj29114342019-10-29 09:30:44 +0100664 db_dict=db_dict,
665 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400666 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100667 )
668 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400669 msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100670 self.log.info(msg)
David Garcia4fee80e2020-05-13 12:18:38 +0200671 raise N2VCExecutionException(e, primitive_name="get-ssh-public-key")
quilesj29114342019-10-29 09:30:44 +0100672
673 # return public key if exists
David Garcia9ae8fa52019-12-09 18:50:03 +0100674 return output["pubkey"] if "pubkey" in output else output
quilesj29114342019-10-29 09:30:44 +0100675
David Garciaeb8943a2021-04-12 12:07:37 +0200676 async def get_metrics(
677 self, model_name: str, application_name: str, vca_id: str = None
678 ) -> dict:
679 """
680 Get metrics from application
681
682 :param: model_name: Model name
683 :param: application_name: Application name
684 :param: vca_id: VCA ID
685
686 :return: Dictionary with obtained metrics
687 """
688 libjuju = await self._get_libjuju(vca_id)
689 return await libjuju.get_metrics(model_name, application_name)
David Garcia85755d12020-09-21 19:51:23 +0200690
quilesj29114342019-10-29 09:30:44 +0100691 async def add_relation(
David Garciaeb8943a2021-04-12 12:07:37 +0200692 self,
693 ee_id_1: str,
694 ee_id_2: str,
695 endpoint_1: str,
696 endpoint_2: str,
697 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100698 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200699 """
700 Add relation between two charmed endpoints
quilesj29114342019-10-29 09:30:44 +0100701
David Garciaeb8943a2021-04-12 12:07:37 +0200702 :param: ee_id_1: The id of the first execution environment
703 :param: ee_id_2: The id of the second execution environment
704 :param: endpoint_1: The endpoint in the first execution environment
705 :param: endpoint_2: The endpoint in the second execution environment
706 :param: vca_id: VCA ID
707 """
beierlmf52cb7c2020-04-21 16:36:35 -0400708 self.log.debug(
709 "adding new relation between {} and {}, endpoints: {}, {}".format(
710 ee_id_1, ee_id_2, endpoint_1, endpoint_2
711 )
712 )
David Garciaeb8943a2021-04-12 12:07:37 +0200713 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100714
quilesjaae10b42020-01-09 08:49:10 +0000715 # check arguments
716 if not ee_id_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400717 message = "EE 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100718 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400719 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_1"])
quilesjaae10b42020-01-09 08:49:10 +0000720 if not ee_id_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400721 message = "EE 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100722 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400723 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_2"])
quilesjaae10b42020-01-09 08:49:10 +0000724 if not endpoint_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400725 message = "endpoint 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100726 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400727 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_1"])
quilesjaae10b42020-01-09 08:49:10 +0000728 if not endpoint_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400729 message = "endpoint 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100730 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400731 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"])
quilesjaae10b42020-01-09 08:49:10 +0000732
quilesjaae10b42020-01-09 08:49:10 +0000733 # get the model, the applications and the machines from the ee_id's
beierlmf52cb7c2020-04-21 16:36:35 -0400734 model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1)
735 model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2)
quilesj29114342019-10-29 09:30:44 +0100736
737 # model must be the same
738 if model_1 != model_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400739 message = "EE models are not the same: {} vs {}".format(ee_id_1, ee_id_2)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100740 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400741 raise N2VCBadArgumentsException(
742 message=message, bad_args=["ee_id_1", "ee_id_2"]
743 )
quilesj29114342019-10-29 09:30:44 +0100744
745 # add juju relations between two applications
746 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200747 await libjuju.add_relation(
quilesjaae10b42020-01-09 08:49:10 +0000748 model_name=model_1,
David Garcia8331f7c2020-08-25 16:10:07 +0200749 endpoint_1="{}:{}".format(app_1, endpoint_1),
750 endpoint_2="{}:{}".format(app_2, endpoint_2),
quilesjaae10b42020-01-09 08:49:10 +0000751 )
quilesj29114342019-10-29 09:30:44 +0100752 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400753 message = "Error adding relation between {} and {}: {}".format(
754 ee_id_1, ee_id_2, e
755 )
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100756 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100757 raise N2VCException(message=message)
758
beierlmf52cb7c2020-04-21 16:36:35 -0400759 async def remove_relation(self):
quilesj29114342019-10-29 09:30:44 +0100760 # TODO
beierlmf52cb7c2020-04-21 16:36:35 -0400761 self.log.info("Method not implemented yet")
762 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100763
beierlmf52cb7c2020-04-21 16:36:35 -0400764 async def deregister_execution_environments(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400765 self.log.info("Method not implemented yet")
766 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100767
768 async def delete_namespace(
David Garciaeb8943a2021-04-12 12:07:37 +0200769 self,
770 namespace: str,
771 db_dict: dict = None,
772 total_timeout: float = None,
773 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100774 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200775 """
776 Remove a network scenario and its execution environments
777 :param: namespace: [<nsi-id>].<ns-id>
778 :param: db_dict: where to write into database when the status changes.
779 It contains a dict with
780 {collection: <str>, filter: {}, path: <str>},
781 e.g. {collection: "nsrs", filter:
782 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
783 :param: total_timeout: Total timeout
784 :param: vca_id: VCA ID
785 """
beierlmf52cb7c2020-04-21 16:36:35 -0400786 self.log.info("Deleting namespace={}".format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200787 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100788
quilesj29114342019-10-29 09:30:44 +0100789 # check arguments
790 if namespace is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400791 raise N2VCBadArgumentsException(
792 message="namespace is mandatory", bad_args=["namespace"]
793 )
quilesj29114342019-10-29 09:30:44 +0100794
beierlmf52cb7c2020-04-21 16:36:35 -0400795 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
796 namespace=namespace
797 )
quilesj29114342019-10-29 09:30:44 +0100798 if ns_id is not None:
quilesj29114342019-10-29 09:30:44 +0100799 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200800 models = await libjuju.list_models(contains=ns_id)
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200801 for model in models:
David Garciaeb8943a2021-04-12 12:07:37 +0200802 await libjuju.destroy_model(
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200803 model_name=model, total_timeout=total_timeout
804 )
quilesj29114342019-10-29 09:30:44 +0100805 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400806 raise N2VCException(
807 message="Error deleting namespace {} : {}".format(namespace, e)
808 )
quilesj29114342019-10-29 09:30:44 +0100809 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400810 raise N2VCBadArgumentsException(
811 message="only ns_id is permitted to delete yet", bad_args=["namespace"]
812 )
quilesj29114342019-10-29 09:30:44 +0100813
beierlmf52cb7c2020-04-21 16:36:35 -0400814 self.log.info("Namespace {} deleted".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100815
816 async def delete_execution_environment(
David Garciaeb8943a2021-04-12 12:07:37 +0200817 self,
818 ee_id: str,
819 db_dict: dict = None,
820 total_timeout: float = None,
821 scaling_in: bool = False,
822 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100823 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200824 """
825 Delete an execution environment
826 :param str ee_id: id of the execution environment to delete
827 :param dict db_dict: where to write into database when the status changes.
828 It contains a dict with
829 {collection: <str>, filter: {}, path: <str>},
830 e.g. {collection: "nsrs", filter:
831 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
832 :param: total_timeout: Total timeout
833 :param: scaling_in: Boolean to indicate if is it a scaling in operation
834 :param: vca_id: VCA ID
835 """
beierlmf52cb7c2020-04-21 16:36:35 -0400836 self.log.info("Deleting execution environment ee_id={}".format(ee_id))
David Garciaeb8943a2021-04-12 12:07:37 +0200837 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100838
quilesj29114342019-10-29 09:30:44 +0100839 # check arguments
840 if ee_id is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400841 raise N2VCBadArgumentsException(
842 message="ee_id is mandatory", bad_args=["ee_id"]
843 )
quilesj29114342019-10-29 09:30:44 +0100844
beierlmf52cb7c2020-04-21 16:36:35 -0400845 model_name, application_name, _machine_id = self._get_ee_id_components(
846 ee_id=ee_id
847 )
quilesj29114342019-10-29 09:30:44 +0100848 try:
aktasd1d55412021-02-21 19:36:20 +0300849 if not scaling_in:
850 # destroy the model
851 # TODO: should this be removed?
David Garciaeb8943a2021-04-12 12:07:37 +0200852 await libjuju.destroy_model(
aktas56120292021-02-26 15:32:39 +0300853 model_name=model_name,
854 total_timeout=total_timeout,
aktasd1d55412021-02-21 19:36:20 +0300855 )
856 else:
aktasd1d55412021-02-21 19:36:20 +0300857 # destroy the application
David Garciaeb8943a2021-04-12 12:07:37 +0200858 await libjuju.destroy_application(
aktas56120292021-02-26 15:32:39 +0300859 model_name=model_name,
860 application_name=application_name,
861 total_timeout=total_timeout,
862 )
quilesj29114342019-10-29 09:30:44 +0100863 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400864 raise N2VCException(
865 message=(
866 "Error deleting execution environment {} (application {}) : {}"
867 ).format(ee_id, application_name, e)
868 )
quilesj29114342019-10-29 09:30:44 +0100869
beierlmf52cb7c2020-04-21 16:36:35 -0400870 self.log.info("Execution environment {} deleted".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100871
872 async def exec_primitive(
beierlmf52cb7c2020-04-21 16:36:35 -0400873 self,
874 ee_id: str,
875 primitive_name: str,
876 params_dict: dict,
877 db_dict: dict = None,
878 progress_timeout: float = None,
879 total_timeout: float = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200880 vca_id: str = None,
quilesj29114342019-10-29 09:30:44 +0100881 ) -> str:
David Garciaeb8943a2021-04-12 12:07:37 +0200882 """
883 Execute a primitive in the execution environment
884
885 :param: ee_id: the one returned by create_execution_environment or
886 register_execution_environment
887 :param: primitive_name: must be one defined in the software. There is one
888 called 'config', where, for the proxy case, the 'credentials' of VM are
889 provided
890 :param: params_dict: parameters of the action
891 :param: db_dict: where to write into database when the status changes.
892 It contains a dict with
893 {collection: <str>, filter: {}, path: <str>},
894 e.g. {collection: "nsrs", filter:
895 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
896 :param: progress_timeout: Progress timeout
897 :param: total_timeout: Total timeout
898 :param: vca_id: VCA ID
899 :returns str: primitive result, if ok. It raises exceptions in case of fail
900 """
quilesj29114342019-10-29 09:30:44 +0100901
beierlmf52cb7c2020-04-21 16:36:35 -0400902 self.log.info(
903 "Executing primitive: {} on ee: {}, params: {}".format(
904 primitive_name, ee_id, params_dict
905 )
906 )
David Garciaeb8943a2021-04-12 12:07:37 +0200907 libjuju = await self._get_libjuju(vca_id)
quilesj29114342019-10-29 09:30:44 +0100908
quilesj29114342019-10-29 09:30:44 +0100909 # check arguments
910 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400911 raise N2VCBadArgumentsException(
912 message="ee_id is mandatory", bad_args=["ee_id"]
913 )
quilesj29114342019-10-29 09:30:44 +0100914 if primitive_name is None or len(primitive_name) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400915 raise N2VCBadArgumentsException(
916 message="action_name is mandatory", bad_args=["action_name"]
917 )
quilesj29114342019-10-29 09:30:44 +0100918 if params_dict is None:
919 params_dict = dict()
920
921 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400922 (
923 model_name,
924 application_name,
925 _machine_id,
926 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
quilesj29114342019-10-29 09:30:44 +0100927 except Exception:
928 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400929 message="ee_id={} is not a valid execution environment id".format(
930 ee_id
931 ),
932 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100933 )
934
beierlmf52cb7c2020-04-21 16:36:35 -0400935 if primitive_name == "config":
quilesj29114342019-10-29 09:30:44 +0100936 # Special case: config primitive
937 try:
David Garciaeb8943a2021-04-12 12:07:37 +0200938 await libjuju.configure_application(
quilesj29114342019-10-29 09:30:44 +0100939 model_name=model_name,
940 application_name=application_name,
941 config=params_dict,
quilesj29114342019-10-29 09:30:44 +0100942 )
David Garciaeb8943a2021-04-12 12:07:37 +0200943 actions = await libjuju.get_actions(
944 application_name=application_name,
945 model_name=model_name,
David Garcia4fee80e2020-05-13 12:18:38 +0200946 )
947 self.log.debug(
948 "Application {} has these actions: {}".format(
949 application_name, actions
950 )
951 )
952 if "verify-ssh-credentials" in actions:
953 # execute verify-credentials
954 num_retries = 20
955 retry_timeout = 15.0
956 for _ in range(num_retries):
957 try:
958 self.log.debug("Executing action verify-ssh-credentials...")
David Garciaeb8943a2021-04-12 12:07:37 +0200959 output, ok = await libjuju.execute_action(
David Garcia4fee80e2020-05-13 12:18:38 +0200960 model_name=model_name,
961 application_name=application_name,
962 action_name="verify-ssh-credentials",
963 db_dict=db_dict,
964 progress_timeout=progress_timeout,
965 total_timeout=total_timeout,
966 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200967
968 if ok == "failed":
969 self.log.debug(
970 "Error executing verify-ssh-credentials: {}. Retrying..."
971 )
972 await asyncio.sleep(retry_timeout)
973
974 continue
David Garcia4fee80e2020-05-13 12:18:38 +0200975 self.log.debug("Result: {}, output: {}".format(ok, output))
976 break
977 except asyncio.CancelledError:
978 raise
David Garcia4fee80e2020-05-13 12:18:38 +0200979 else:
980 self.log.error(
981 "Error executing verify-ssh-credentials after {} retries. ".format(
982 num_retries
983 )
984 )
985 else:
986 msg = "Action verify-ssh-credentials does not exist in application {}".format(
987 application_name
988 )
989 self.log.debug(msg=msg)
quilesj29114342019-10-29 09:30:44 +0100990 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400991 self.log.error("Error configuring juju application: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100992 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400993 message="Error configuring application into ee={} : {}".format(
994 ee_id, e
995 ),
996 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100997 )
beierlmf52cb7c2020-04-21 16:36:35 -0400998 return "CONFIG OK"
quilesj29114342019-10-29 09:30:44 +0100999 else:
1000 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001001 output, status = await libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +01001002 model_name=model_name,
1003 application_name=application_name,
1004 action_name=primitive_name,
1005 db_dict=db_dict,
1006 progress_timeout=progress_timeout,
1007 total_timeout=total_timeout,
1008 **params_dict
1009 )
beierlmf52cb7c2020-04-21 16:36:35 -04001010 if status == "completed":
quilesj29114342019-10-29 09:30:44 +01001011 return output
1012 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001013 raise Exception("status is not completed: {}".format(status))
quilesj29114342019-10-29 09:30:44 +01001014 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001015 self.log.error(
1016 "Error executing primitive {}: {}".format(primitive_name, e)
1017 )
quilesj29114342019-10-29 09:30:44 +01001018 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -04001019 message="Error executing primitive {} into ee={} : {}".format(
1020 primitive_name, ee_id, e
1021 ),
1022 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +01001023 )
1024
David Garciaeb8943a2021-04-12 12:07:37 +02001025 async def disconnect(self, vca_id: str = None):
1026 """
1027 Disconnect from VCA
1028
1029 :param: vca_id: VCA ID
1030 """
beierlmf52cb7c2020-04-21 16:36:35 -04001031 self.log.info("closing juju N2VC...")
David Garciaeb8943a2021-04-12 12:07:37 +02001032 libjuju = await self._get_libjuju(vca_id)
David Garcia4fee80e2020-05-13 12:18:38 +02001033 try:
David Garciaeb8943a2021-04-12 12:07:37 +02001034 await libjuju.disconnect()
David Garcia4fee80e2020-05-13 12:18:38 +02001035 except Exception as e:
1036 raise N2VCConnectionException(
David Garciaeb8943a2021-04-12 12:07:37 +02001037 message="Error disconnecting controller: {}".format(e),
1038 url=libjuju.vca_connection.data.endpoints,
David Garcia4fee80e2020-05-13 12:18:38 +02001039 )
quilesj29114342019-10-29 09:30:44 +01001040
1041 """
David Garcia4fee80e2020-05-13 12:18:38 +02001042####################################################################################
1043################################### P R I V A T E ##################################
1044####################################################################################
quilesj29114342019-10-29 09:30:44 +01001045 """
1046
David Garciaeb8943a2021-04-12 12:07:37 +02001047 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
1048 """
1049 Get libjuju object
1050
1051 :param: vca_id: VCA ID
1052 If None, get a libjuju object with a Connection to the default VCA
1053 Else, geta libjuju object with a Connection to the specified VCA
1054 """
1055 if not vca_id:
1056 while self.loading_libjuju.locked():
1057 await asyncio.sleep(0.1)
1058 if not self.libjuju:
1059 async with self.loading_libjuju:
1060 vca_connection = await get_connection(self._store)
1061 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
1062 return self.libjuju
1063 else:
1064 vca_connection = await get_connection(self._store, vca_id)
1065 return Libjuju(
1066 vca_connection,
1067 loop=self.loop,
1068 log=self.log,
1069 n2vc=self,
1070 )
1071
beierlmf52cb7c2020-04-21 16:36:35 -04001072 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +01001073
1074 # write ee_id to database: _admin.deployed.VCA.x
1075 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001076 the_table = db_dict["collection"]
1077 the_filter = db_dict["filter"]
1078 the_path = db_dict["path"]
1079 if not the_path[-1] == ".":
1080 the_path = the_path + "."
1081 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001082 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +01001083 self.db.set_one(
1084 table=the_table,
1085 q_filter=the_filter,
1086 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -04001087 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +01001088 )
tierno8ff11992020-03-26 09:51:11 +00001089 except asyncio.CancelledError:
1090 raise
quilesj29114342019-10-29 09:30:44 +01001091 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001092 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +01001093
1094 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001095 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +01001096 """
1097 Build an execution environment id form model, application and machine
1098 :param model_name:
1099 :param application_name:
1100 :param machine_id:
1101 :return:
1102 """
1103 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -04001104 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +01001105
1106 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001107 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +01001108 """
1109 Get model, application and machine components from an execution environment id
1110 :param ee_id:
1111 :return: model_name, application_name, machine_id
1112 """
1113
1114 if ee_id is None:
1115 return None, None, None
1116
1117 # split components of id
beierlmf52cb7c2020-04-21 16:36:35 -04001118 parts = ee_id.split(".")
quilesj29114342019-10-29 09:30:44 +01001119 model_name = parts[0]
1120 application_name = parts[1]
1121 machine_id = parts[2]
1122 return model_name, application_name, machine_id
1123
1124 def _get_application_name(self, namespace: str) -> str:
1125 """
1126 Build application name from namespace
1127 :param namespace:
1128 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1129 """
1130
Adam Israel18046072019-12-08 21:44:29 -05001131 # TODO: Enforce the Juju 50-character application limit
1132
quilesj29114342019-10-29 09:30:44 +01001133 # split namespace components
beierlmf52cb7c2020-04-21 16:36:35 -04001134 _, _, vnf_id, vdu_id, vdu_count = self._get_namespace_components(
1135 namespace=namespace
1136 )
quilesj29114342019-10-29 09:30:44 +01001137
1138 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001139 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001140 else:
Adam Israel18046072019-12-08 21:44:29 -05001141 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001142 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001143
1144 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001145 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001146 else:
Adam Israel18046072019-12-08 21:44:29 -05001147 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001148 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001149
1150 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001151 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001152 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001153 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001154
beierlmf52cb7c2020-04-21 16:36:35 -04001155 application_name = "app-{}{}{}".format(vnf_id, vdu_id, vdu_count)
quilesj29114342019-10-29 09:30:44 +01001156
1157 return N2VCJujuConnector._format_app_name(application_name)
1158
quilesj29114342019-10-29 09:30:44 +01001159 @staticmethod
1160 def _format_model_name(name: str) -> str:
1161 """Format the name of the model.
1162
1163 Model names may only contain lowercase letters, digits and hyphens
1164 """
1165
beierlmf52cb7c2020-04-21 16:36:35 -04001166 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001167
1168 @staticmethod
1169 def _format_app_name(name: str) -> str:
1170 """Format the name of the application (in order to assure valid application name).
1171
1172 Application names have restrictions (run juju deploy --help):
1173 - contains lowercase letters 'a'-'z'
1174 - contains numbers '0'-'9'
1175 - contains hyphens '-'
1176 - starts with a lowercase letter
1177 - not two or more consecutive hyphens
1178 - after a hyphen, not a group with all numbers
1179 """
1180
1181 def all_numbers(s: str) -> bool:
1182 for c in s:
1183 if not c.isdigit():
1184 return False
1185 return True
1186
beierlmf52cb7c2020-04-21 16:36:35 -04001187 new_name = name.replace("_", "-")
1188 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001189 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001190 while new_name.find("--") >= 0:
1191 new_name = new_name.replace("--", "-")
1192 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001193
1194 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001195 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001196 for i in range(len(groups)):
1197 group = groups[i]
1198 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001199 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001200 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001201 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001202 app_name += group
1203
1204 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001205 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001206
1207 return app_name
David Garciaeb8943a2021-04-12 12:07:37 +02001208
1209 async def validate_vca(self, vca_id: str):
1210 """
1211 Validate a VCA by connecting/disconnecting to/from it
1212
1213 :param: vca_id: VCA ID
1214 """
1215 vca_connection = await get_connection(self._store, vca_id=vca_id)
1216 libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self)
1217 controller = await libjuju.get_controller()
1218 await libjuju.disconnect_controller(controller)