blob: d31c169932cbabffdaed24c250bfb66b34c931fa [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
25import os
quilesj29114342019-10-29 09:30:44 +010026
David Garciaa71d4a02021-03-10 20:00:53 +010027from n2vc.config import ModelConfig
beierlmf52cb7c2020-04-21 16:36:35 -040028from n2vc.exceptions import (
29 N2VCBadArgumentsException,
30 N2VCException,
31 N2VCConnectionException,
32 N2VCExecutionException,
almagiaba6e5322020-09-16 09:44:40 +020033 # N2VCNotFound,
beierlmf52cb7c2020-04-21 16:36:35 -040034 MethodNotImplemented,
Dominik Fleischmannb9513342020-06-09 11:57:14 +020035 JujuK8sProxycharmNotSupported,
beierlmf52cb7c2020-04-21 16:36:35 -040036)
quilesj29114342019-10-29 09:30:44 +010037from n2vc.n2vc_conn import N2VCConnector
quilesj776ab392019-12-12 16:10:54 +000038from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
David Garcia4fee80e2020-05-13 12:18:38 +020039from n2vc.libjuju import Libjuju
David Garcia475a7222020-09-21 16:19:15 +020040from n2vc.utils import base64_to_cacert
quilesj29114342019-10-29 09:30:44 +010041
42
43class N2VCJujuConnector(N2VCConnector):
44
45 """
David Garcia4fee80e2020-05-13 12:18:38 +020046####################################################################################
47################################### P U B L I C ####################################
48####################################################################################
quilesj29114342019-10-29 09:30:44 +010049 """
50
David Garcia347aae62020-04-29 12:34:23 +020051 BUILT_IN_CLOUDS = ["localhost", "microk8s"]
52
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 url: str = "127.0.0.1:17070",
60 username: str = "admin",
quilesj29114342019-10-29 09:30:44 +010061 vca_config: dict = None,
beierlmf52cb7c2020-04-21 16:36:35 -040062 on_update_db=None,
quilesj29114342019-10-29 09:30:44 +010063 ):
64 """Initialize juju N2VC connector
65 """
66
67 # parent class constructor
68 N2VCConnector.__init__(
69 self,
70 db=db,
71 fs=fs,
72 log=log,
73 loop=loop,
74 url=url,
75 username=username,
76 vca_config=vca_config,
beierlmf52cb7c2020-04-21 16:36:35 -040077 on_update_db=on_update_db,
quilesj29114342019-10-29 09:30:44 +010078 )
79
80 # silence websocket traffic log
beierlmf52cb7c2020-04-21 16:36:35 -040081 logging.getLogger("websockets.protocol").setLevel(logging.INFO)
82 logging.getLogger("juju.client.connection").setLevel(logging.WARN)
83 logging.getLogger("model").setLevel(logging.WARN)
quilesj29114342019-10-29 09:30:44 +010084
beierlmf52cb7c2020-04-21 16:36:35 -040085 self.log.info("Initializing N2VC juju connector...")
quilesj29114342019-10-29 09:30:44 +010086
87 """
88 ##############################################################
89 # check arguments
90 ##############################################################
91 """
92
93 # juju URL
94 if url is None:
beierlmf52cb7c2020-04-21 16:36:35 -040095 raise N2VCBadArgumentsException("Argument url is mandatory", ["url"])
96 url_parts = url.split(":")
quilesj29114342019-10-29 09:30:44 +010097 if len(url_parts) != 2:
beierlmf52cb7c2020-04-21 16:36:35 -040098 raise N2VCBadArgumentsException(
99 "Argument url: bad format (localhost:port) -> {}".format(url), ["url"]
100 )
quilesj29114342019-10-29 09:30:44 +0100101 self.hostname = url_parts[0]
102 try:
103 self.port = int(url_parts[1])
104 except ValueError:
beierlmf52cb7c2020-04-21 16:36:35 -0400105 raise N2VCBadArgumentsException(
106 "url port must be a number -> {}".format(url), ["url"]
107 )
quilesj29114342019-10-29 09:30:44 +0100108
109 # juju USERNAME
110 if username is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400111 raise N2VCBadArgumentsException(
112 "Argument username is mandatory", ["username"]
113 )
quilesj29114342019-10-29 09:30:44 +0100114
115 # juju CONFIGURATION
116 if vca_config is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400117 raise N2VCBadArgumentsException(
118 "Argument vca_config is mandatory", ["vca_config"]
119 )
quilesj29114342019-10-29 09:30:44 +0100120
beierlmf52cb7c2020-04-21 16:36:35 -0400121 if "secret" in vca_config:
122 self.secret = vca_config["secret"]
quilesj29114342019-10-29 09:30:44 +0100123 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400124 raise N2VCBadArgumentsException(
125 "Argument vca_config.secret is mandatory", ["vca_config.secret"]
126 )
quilesj29114342019-10-29 09:30:44 +0100127
128 # pubkey of juju client in osm machine: ~/.local/share/juju/ssh/juju_id_rsa.pub
129 # if exists, it will be written in lcm container: _create_juju_public_key()
beierlmf52cb7c2020-04-21 16:36:35 -0400130 if "public_key" in vca_config:
131 self.public_key = vca_config["public_key"]
quilesj29114342019-10-29 09:30:44 +0100132 else:
133 self.public_key = None
134
135 # TODO: Verify ca_cert is valid before using. VCA will crash
136 # if the ca_cert isn't formatted correctly.
quilesj29114342019-10-29 09:30:44 +0100137
beierlmf52cb7c2020-04-21 16:36:35 -0400138 self.ca_cert = vca_config.get("ca_cert")
quilesj29114342019-10-29 09:30:44 +0100139 if self.ca_cert:
beierlmf52cb7c2020-04-21 16:36:35 -0400140 self.ca_cert = base64_to_cacert(vca_config["ca_cert"])
quilesj29114342019-10-29 09:30:44 +0100141
David Garcia81045962020-07-16 12:37:13 +0200142 if "api_proxy" in vca_config and vca_config["api_proxy"] != "":
beierlmf52cb7c2020-04-21 16:36:35 -0400143 self.api_proxy = vca_config["api_proxy"]
144 self.log.debug(
145 "api_proxy for native charms configured: {}".format(self.api_proxy)
146 )
quilesj29114342019-10-29 09:30:44 +0100147 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400148 self.warning(
David Garcia81045962020-07-16 12:37:13 +0200149 "api_proxy is not configured"
beierlmf52cb7c2020-04-21 16:36:35 -0400150 )
tiernoec8a5042020-06-24 13:57:10 +0000151 self.api_proxy = None
quilesj29114342019-10-29 09:30:44 +0100152
David Garciaa71d4a02021-03-10 20:00:53 +0100153 model_config = ModelConfig(vca_config)
garciadeblas923510c2019-12-17 15:02:11 +0100154
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200155 self.cloud = vca_config.get('cloud')
156 self.k8s_cloud = None
157 if "k8s_cloud" in vca_config:
158 self.k8s_cloud = vca_config.get("k8s_cloud")
159 self.log.debug('Arguments have been checked')
quilesj29114342019-10-29 09:30:44 +0100160
161 # juju data
beierlmf52cb7c2020-04-21 16:36:35 -0400162 self.controller = None # it will be filled when connect to juju
163 self.juju_models = {} # model objects for every model_name
164 self.juju_observers = {} # model observers for every model_name
165 self._connecting = (
166 False # while connecting to juju (to avoid duplicate connections)
167 )
168 self._authenticated = (
169 False # it will be True when juju connection be stablished
170 )
171 self._creating_model = False # True during model creation
David Garcia4fee80e2020-05-13 12:18:38 +0200172 self.libjuju = Libjuju(
173 endpoint=self.url,
174 api_proxy=self.api_proxy,
David Garcia4fee80e2020-05-13 12:18:38 +0200175 username=self.username,
176 password=self.secret,
177 cacert=self.ca_cert,
178 loop=self.loop,
179 log=self.log,
180 db=self.db,
181 n2vc=self,
David Garciaa71d4a02021-03-10 20:00:53 +0100182 model_config=model_config,
David Garcia4fee80e2020-05-13 12:18:38 +0200183 )
quilesj29114342019-10-29 09:30:44 +0100184
beierlmf52cb7c2020-04-21 16:36:35 -0400185 # create juju pub key file in lcm container at
186 # ./local/share/juju/ssh/juju_id_rsa.pub
quilesj29114342019-10-29 09:30:44 +0100187 self._create_juju_public_key()
188
beierlmf52cb7c2020-04-21 16:36:35 -0400189 self.log.info("N2VC juju connector initialized")
quilesj29114342019-10-29 09:30:44 +0100190
quilesj776ab392019-12-12 16:10:54 +0000191 async def get_status(self, namespace: str, yaml_format: bool = True):
192
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100193 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
quilesj29114342019-10-29 09:30:44 +0100194
beierlmf52cb7c2020-04-21 16:36:35 -0400195 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
196 namespace=namespace
197 )
quilesj29114342019-10-29 09:30:44 +0100198 # model name is ns_id
199 model_name = ns_id
200 if model_name is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400201 msg = "Namespace {} not valid".format(namespace)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100202 self.log.error(msg)
beierlmf52cb7c2020-04-21 16:36:35 -0400203 raise N2VCBadArgumentsException(msg, ["namespace"])
quilesj29114342019-10-29 09:30:44 +0100204
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200205 status = {}
206 models = await self.libjuju.list_models(contains=ns_id)
207
208 for m in models:
David Garciad745e222020-06-30 08:39:26 +0200209 status[m] = await self.libjuju.get_model_status(m)
quilesj776ab392019-12-12 16:10:54 +0000210 if yaml_format:
211 return obj_to_yaml(status)
212 else:
213 return obj_to_dict(status)
quilesj29114342019-10-29 09:30:44 +0100214
ksaikiranrcdf0b8e2021-03-17 12:50:00 +0530215 async def update_vca_status(self, vcastatus: dict):
216 """
217 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
218 :param vcastatus: dict containing vcaStatus
219 :return: None
220 """
221 try:
222 for model_name in vcastatus:
223 # Adding executed actions
224 vcastatus[model_name]["executedActions"] = \
225 await self.libjuju.get_executed_actions(model_name)
226 for application in vcastatus[model_name]["applications"]:
227 # Adding application actions
228 vcastatus[model_name]["applications"][application]["actions"] = \
229 await self.libjuju.get_actions(application, model_name)
230 # Adding application configs
231 vcastatus[model_name]["applications"][application]["configs"] = \
232 await self.libjuju.get_application_configs(model_name, application)
233 except Exception as e:
234 self.log.debug("Error in updating vca status: {}".format(str(e)))
235
quilesj29114342019-10-29 09:30:44 +0100236 async def create_execution_environment(
237 self,
238 namespace: str,
239 db_dict: dict,
240 reuse_ee_id: str = None,
241 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400242 total_timeout: float = None,
David Garcia9a63e8d2020-11-03 20:37:06 +0100243 cloud_name: str = None,
244 credential_name: str = None,
quilesj29114342019-10-29 09:30:44 +0100245 ) -> (str, dict):
246
beierlmf52cb7c2020-04-21 16:36:35 -0400247 self.log.info(
248 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
249 namespace, reuse_ee_id
250 )
251 )
quilesj29114342019-10-29 09:30:44 +0100252
quilesj29114342019-10-29 09:30:44 +0100253 machine_id = None
254 if reuse_ee_id:
beierlmf52cb7c2020-04-21 16:36:35 -0400255 model_name, application_name, machine_id = self._get_ee_id_components(
256 ee_id=reuse_ee_id
257 )
quilesj29114342019-10-29 09:30:44 +0100258 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400259 (
260 _nsi_id,
261 ns_id,
262 _vnf_id,
263 _vdu_id,
264 _vdu_count,
265 ) = self._get_namespace_components(namespace=namespace)
quilesj29114342019-10-29 09:30:44 +0100266 # model name is ns_id
267 model_name = ns_id
268 # application name
269 application_name = self._get_application_name(namespace=namespace)
270
beierlmf52cb7c2020-04-21 16:36:35 -0400271 self.log.debug(
272 "model name: {}, application name: {}, machine_id: {}".format(
273 model_name, application_name, machine_id
274 )
275 )
quilesj29114342019-10-29 09:30:44 +0100276
277 # create or reuse a new juju machine
278 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200279 if not await self.libjuju.model_exists(model_name):
David Garcia9a63e8d2020-11-03 20:37:06 +0100280 cloud = cloud_name or self.cloud
281 credential = credential_name or cloud_name if cloud_name else self.cloud
282 await self.libjuju.add_model(
283 model_name,
284 cloud_name=cloud,
285 credential_name=credential
286 )
David Garcia4fee80e2020-05-13 12:18:38 +0200287 machine, new = await self.libjuju.create_machine(
quilesj29114342019-10-29 09:30:44 +0100288 model_name=model_name,
quilesj29114342019-10-29 09:30:44 +0100289 machine_id=machine_id,
290 db_dict=db_dict,
291 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400292 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100293 )
David Garcia4fee80e2020-05-13 12:18:38 +0200294 # id for the execution environment
295 ee_id = N2VCJujuConnector._build_ee_id(
296 model_name=model_name,
297 application_name=application_name,
298 machine_id=str(machine.entity_id),
299 )
300 self.log.debug("ee_id: {}".format(ee_id))
301
302 if new:
303 # write ee_id in database
304 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
305
quilesj29114342019-10-29 09:30:44 +0100306 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400307 message = "Error creating machine on juju: {}".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100308 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100309 raise N2VCException(message=message)
310
quilesj29114342019-10-29 09:30:44 +0100311 # new machine credentials
David Garcia4fee80e2020-05-13 12:18:38 +0200312 credentials = {
313 "hostname": machine.dns_name,
314 }
quilesj29114342019-10-29 09:30:44 +0100315
beierlmf52cb7c2020-04-21 16:36:35 -0400316 self.log.info(
317 "Execution environment created. ee_id: {}, credentials: {}".format(
318 ee_id, credentials
319 )
320 )
quilesj29114342019-10-29 09:30:44 +0100321
322 return ee_id, credentials
323
324 async def register_execution_environment(
325 self,
326 namespace: str,
327 credentials: dict,
328 db_dict: dict,
329 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400330 total_timeout: float = None,
David Garcia9a63e8d2020-11-03 20:37:06 +0100331 cloud_name: str = None,
332 credential_name: str = None,
quilesj29114342019-10-29 09:30:44 +0100333 ) -> str:
334
beierlmf52cb7c2020-04-21 16:36:35 -0400335 self.log.info(
336 "Registering execution environment. namespace={}, credentials={}".format(
337 namespace, credentials
338 )
339 )
quilesj29114342019-10-29 09:30:44 +0100340
341 if credentials is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400342 raise N2VCBadArgumentsException(
343 message="credentials are mandatory", bad_args=["credentials"]
344 )
345 if credentials.get("hostname"):
346 hostname = credentials["hostname"]
quilesj29114342019-10-29 09:30:44 +0100347 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400348 raise N2VCBadArgumentsException(
349 message="hostname is mandatory", bad_args=["credentials.hostname"]
350 )
351 if credentials.get("username"):
352 username = credentials["username"]
quilesj29114342019-10-29 09:30:44 +0100353 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400354 raise N2VCBadArgumentsException(
355 message="username is mandatory", bad_args=["credentials.username"]
356 )
357 if "private_key_path" in credentials:
358 private_key_path = credentials["private_key_path"]
quilesj29114342019-10-29 09:30:44 +0100359 else:
360 # if not passed as argument, use generated private key path
361 private_key_path = self.private_key_path
362
beierlmf52cb7c2020-04-21 16:36:35 -0400363 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
364 namespace=namespace
365 )
quilesj29114342019-10-29 09:30:44 +0100366
367 # model name
368 model_name = ns_id
369 # application name
370 application_name = self._get_application_name(namespace=namespace)
371
372 # register machine on juju
373 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200374 if not await self.libjuju.model_exists(model_name):
David Garcia9a63e8d2020-11-03 20:37:06 +0100375 cloud = cloud_name or self.cloud
376 credential = credential_name or cloud_name if cloud_name else self.cloud
377 await self.libjuju.add_model(
378 model_name,
379 cloud_name=cloud,
380 credential_name=credential
381 )
David Garcia4fee80e2020-05-13 12:18:38 +0200382 machine_id = await self.libjuju.provision_machine(
quilesj29114342019-10-29 09:30:44 +0100383 model_name=model_name,
384 hostname=hostname,
385 username=username,
386 private_key_path=private_key_path,
387 db_dict=db_dict,
388 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400389 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100390 )
391 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400392 self.log.error("Error registering machine: {}".format(e))
393 raise N2VCException(
394 message="Error registering machine on juju: {}".format(e)
395 )
quilesjac4e0de2019-11-27 16:12:02 +0000396
beierlmf52cb7c2020-04-21 16:36:35 -0400397 self.log.info("Machine registered: {}".format(machine_id))
quilesj29114342019-10-29 09:30:44 +0100398
399 # id for the execution environment
400 ee_id = N2VCJujuConnector._build_ee_id(
401 model_name=model_name,
402 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400403 machine_id=str(machine_id),
quilesj29114342019-10-29 09:30:44 +0100404 )
405
beierlmf52cb7c2020-04-21 16:36:35 -0400406 self.log.info("Execution environment registered. ee_id: {}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100407
408 return ee_id
409
410 async def install_configuration_sw(
411 self,
412 ee_id: str,
413 artifact_path: str,
414 db_dict: dict,
415 progress_timeout: float = None,
David Garciadfaa6e82020-04-01 16:06:39 +0200416 total_timeout: float = None,
417 config: dict = None,
David Garciaf8a9d462020-03-25 18:19:02 +0100418 num_units: int = 1,
quilesj29114342019-10-29 09:30:44 +0100419 ):
420
beierlmf52cb7c2020-04-21 16:36:35 -0400421 self.log.info(
422 (
423 "Installing configuration sw on ee_id: {}, "
424 "artifact path: {}, db_dict: {}"
425 ).format(ee_id, artifact_path, db_dict)
426 )
quilesj29114342019-10-29 09:30:44 +0100427
quilesj29114342019-10-29 09:30:44 +0100428 # check arguments
429 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400430 raise N2VCBadArgumentsException(
431 message="ee_id is mandatory", bad_args=["ee_id"]
432 )
quilesj29114342019-10-29 09:30:44 +0100433 if artifact_path is None or len(artifact_path) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400434 raise N2VCBadArgumentsException(
435 message="artifact_path is mandatory", bad_args=["artifact_path"]
436 )
quilesj29114342019-10-29 09:30:44 +0100437 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400438 raise N2VCBadArgumentsException(
439 message="db_dict is mandatory", bad_args=["db_dict"]
440 )
quilesj29114342019-10-29 09:30:44 +0100441
442 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400443 (
444 model_name,
445 application_name,
446 machine_id,
447 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
448 self.log.debug(
449 "model: {}, application: {}, machine: {}".format(
450 model_name, application_name, machine_id
451 )
452 )
453 except Exception:
quilesj29114342019-10-29 09:30:44 +0100454 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400455 message="ee_id={} is not a valid execution environment id".format(
456 ee_id
457 ),
458 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100459 )
460
461 # remove // in charm path
beierlmf52cb7c2020-04-21 16:36:35 -0400462 while artifact_path.find("//") >= 0:
463 artifact_path = artifact_path.replace("//", "/")
quilesj29114342019-10-29 09:30:44 +0100464
465 # check charm path
466 if not self.fs.file_exists(artifact_path, mode="dir"):
beierlmf52cb7c2020-04-21 16:36:35 -0400467 msg = "artifact path does not exist: {}".format(artifact_path)
468 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
quilesj29114342019-10-29 09:30:44 +0100469
beierlmf52cb7c2020-04-21 16:36:35 -0400470 if artifact_path.startswith("/"):
quilesj29114342019-10-29 09:30:44 +0100471 full_path = self.fs.path + artifact_path
472 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400473 full_path = self.fs.path + "/" + artifact_path
quilesj29114342019-10-29 09:30:44 +0100474
475 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200476 await self.libjuju.deploy_charm(
quilesj29114342019-10-29 09:30:44 +0100477 model_name=model_name,
478 application_name=application_name,
David Garcia4fee80e2020-05-13 12:18:38 +0200479 path=full_path,
quilesj29114342019-10-29 09:30:44 +0100480 machine_id=machine_id,
481 db_dict=db_dict,
482 progress_timeout=progress_timeout,
David Garciadfaa6e82020-04-01 16:06:39 +0200483 total_timeout=total_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400484 config=config,
David Garciaf8a9d462020-03-25 18:19:02 +0100485 num_units=num_units,
quilesj29114342019-10-29 09:30:44 +0100486 )
487 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400488 raise N2VCException(
489 message="Error desploying charm into ee={} : {}".format(ee_id, e)
490 )
quilesj29114342019-10-29 09:30:44 +0100491
beierlmf52cb7c2020-04-21 16:36:35 -0400492 self.log.info("Configuration sw installed")
quilesj29114342019-10-29 09:30:44 +0100493
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200494 async def install_k8s_proxy_charm(
495 self,
496 charm_name: str,
497 namespace: str,
498 artifact_path: str,
499 db_dict: dict,
500 progress_timeout: float = None,
501 total_timeout: float = None,
502 config: dict = None,
David Garcia9a63e8d2020-11-03 20:37:06 +0100503 cloud_name: str = None,
504 credential_name: str = None,
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200505 ) -> str:
506 """
507 Install a k8s proxy charm
508
509 :param charm_name: Name of the charm being deployed
510 :param namespace: collection of all the uuids related to the charm.
511 :param str artifact_path: where to locate the artifacts (parent folder) using
512 the self.fs
513 the final artifact path will be a combination of this artifact_path and
514 additional string from the config_dict (e.g. charm name)
515 :param dict db_dict: where to write into database when the status changes.
516 It contains a dict with
517 {collection: <str>, filter: {}, path: <str>},
518 e.g. {collection: "nsrs", filter:
519 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
520 :param float progress_timeout:
521 :param float total_timeout:
522 :param config: Dictionary with additional configuration
David Garcia9a63e8d2020-11-03 20:37:06 +0100523 :param cloud_name: Cloud Name in which the charms will be deployed
524 :param credential_name: Credential Name to use in the cloud_name.
525 If not set, cloud_name will be used as credential_name
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200526
527 :returns ee_id: execution environment id.
528 """
529 self.log.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
530 .format(charm_name, artifact_path, db_dict))
531
532 if not self.k8s_cloud:
533 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
534
535 if artifact_path is None or len(artifact_path) == 0:
536 raise N2VCBadArgumentsException(
537 message="artifact_path is mandatory", bad_args=["artifact_path"]
538 )
539 if db_dict is None:
540 raise N2VCBadArgumentsException(message='db_dict is mandatory', bad_args=['db_dict'])
541
542 # remove // in charm path
543 while artifact_path.find('//') >= 0:
544 artifact_path = artifact_path.replace('//', '/')
545
546 # check charm path
547 if not self.fs.file_exists(artifact_path, mode="dir"):
548 msg = 'artifact path does not exist: {}'.format(artifact_path)
549 raise N2VCBadArgumentsException(message=msg, bad_args=['artifact_path'])
550
551 if artifact_path.startswith('/'):
552 full_path = self.fs.path + artifact_path
553 else:
554 full_path = self.fs.path + '/' + artifact_path
555
556 _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace)
557 model_name = '{}-k8s'.format(ns_id)
aktasd1d55412021-02-21 19:36:20 +0300558 if not await self.libjuju.model_exists(model_name):
559 cloud = cloud_name or self.k8s_cloud
560 credential = credential_name or cloud_name if cloud_name else self.k8s_cloud
561 await self.libjuju.add_model(
562 model_name,
563 cloud_name=cloud,
564 credential_name=credential
565 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200566 application_name = self._get_application_name(namespace)
567
568 try:
569 await self.libjuju.deploy_charm(
570 model_name=model_name,
571 application_name=application_name,
572 path=full_path,
573 machine_id=None,
574 db_dict=db_dict,
575 progress_timeout=progress_timeout,
576 total_timeout=total_timeout,
577 config=config
578 )
579 except Exception as e:
580 raise N2VCException(message='Error deploying charm: {}'.format(e))
581
582 self.log.info('K8s proxy charm installed')
583 ee_id = N2VCJujuConnector._build_ee_id(
584 model_name=model_name,
585 application_name=application_name,
586 machine_id="k8s",
587 )
Dominik Fleischmann7ace6fa2020-06-29 16:16:28 +0200588
589 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
590
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200591 return ee_id
592
quilesj29114342019-10-29 09:30:44 +0100593 async def get_ee_ssh_public__key(
594 self,
595 ee_id: str,
596 db_dict: dict,
597 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400598 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100599 ) -> str:
600
beierlmf52cb7c2020-04-21 16:36:35 -0400601 self.log.info(
602 (
603 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
604 ).format(ee_id, db_dict)
605 )
quilesj29114342019-10-29 09:30:44 +0100606
quilesj29114342019-10-29 09:30:44 +0100607 # check arguments
608 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400609 raise N2VCBadArgumentsException(
610 message="ee_id is mandatory", bad_args=["ee_id"]
611 )
quilesj29114342019-10-29 09:30:44 +0100612 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400613 raise N2VCBadArgumentsException(
614 message="db_dict is mandatory", bad_args=["db_dict"]
615 )
quilesj29114342019-10-29 09:30:44 +0100616
617 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400618 (
619 model_name,
620 application_name,
621 machine_id,
622 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
623 self.log.debug(
624 "model: {}, application: {}, machine: {}".format(
625 model_name, application_name, machine_id
626 )
627 )
628 except Exception:
quilesj29114342019-10-29 09:30:44 +0100629 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400630 message="ee_id={} is not a valid execution environment id".format(
631 ee_id
632 ),
633 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100634 )
635
636 # try to execute ssh layer primitives (if exist):
637 # generate-ssh-key
638 # get-ssh-public-key
639
640 output = None
641
David Garcia4fee80e2020-05-13 12:18:38 +0200642 application_name = N2VCJujuConnector._format_app_name(application_name)
643
quilesj29114342019-10-29 09:30:44 +0100644 # execute action: generate-ssh-key
645 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200646 output, _status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100647 model_name=model_name,
648 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400649 action_name="generate-ssh-key",
quilesj29114342019-10-29 09:30:44 +0100650 db_dict=db_dict,
651 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400652 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100653 )
654 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400655 self.log.info(
656 "Skipping exception while executing action generate-ssh-key: {}".format(
657 e
658 )
659 )
quilesj29114342019-10-29 09:30:44 +0100660
661 # execute action: get-ssh-public-key
662 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200663 output, _status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100664 model_name=model_name,
665 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400666 action_name="get-ssh-public-key",
quilesj29114342019-10-29 09:30:44 +0100667 db_dict=db_dict,
668 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400669 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100670 )
671 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400672 msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100673 self.log.info(msg)
David Garcia4fee80e2020-05-13 12:18:38 +0200674 raise N2VCExecutionException(e, primitive_name="get-ssh-public-key")
quilesj29114342019-10-29 09:30:44 +0100675
676 # return public key if exists
David Garcia9ae8fa52019-12-09 18:50:03 +0100677 return output["pubkey"] if "pubkey" in output else output
quilesj29114342019-10-29 09:30:44 +0100678
David Garcia85755d12020-09-21 19:51:23 +0200679 async def get_metrics(self, model_name: str, application_name: str) -> dict:
680 return await self.libjuju.get_metrics(model_name, application_name)
681
quilesj29114342019-10-29 09:30:44 +0100682 async def add_relation(
beierlmf52cb7c2020-04-21 16:36:35 -0400683 self, ee_id_1: str, ee_id_2: str, endpoint_1: str, endpoint_2: str
quilesj29114342019-10-29 09:30:44 +0100684 ):
685
beierlmf52cb7c2020-04-21 16:36:35 -0400686 self.log.debug(
687 "adding new relation between {} and {}, endpoints: {}, {}".format(
688 ee_id_1, ee_id_2, endpoint_1, endpoint_2
689 )
690 )
quilesj29114342019-10-29 09:30:44 +0100691
quilesjaae10b42020-01-09 08:49:10 +0000692 # check arguments
693 if not ee_id_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400694 message = "EE 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100695 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400696 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_1"])
quilesjaae10b42020-01-09 08:49:10 +0000697 if not ee_id_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400698 message = "EE 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100699 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400700 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_2"])
quilesjaae10b42020-01-09 08:49:10 +0000701 if not endpoint_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400702 message = "endpoint 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100703 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400704 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_1"])
quilesjaae10b42020-01-09 08:49:10 +0000705 if not endpoint_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400706 message = "endpoint 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100707 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400708 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"])
quilesjaae10b42020-01-09 08:49:10 +0000709
quilesjaae10b42020-01-09 08:49:10 +0000710 # get the model, the applications and the machines from the ee_id's
beierlmf52cb7c2020-04-21 16:36:35 -0400711 model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1)
712 model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2)
quilesj29114342019-10-29 09:30:44 +0100713
714 # model must be the same
715 if model_1 != model_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400716 message = "EE models are not the same: {} vs {}".format(ee_id_1, ee_id_2)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100717 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400718 raise N2VCBadArgumentsException(
719 message=message, bad_args=["ee_id_1", "ee_id_2"]
720 )
quilesj29114342019-10-29 09:30:44 +0100721
722 # add juju relations between two applications
723 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200724 await self.libjuju.add_relation(
quilesjaae10b42020-01-09 08:49:10 +0000725 model_name=model_1,
David Garcia8331f7c2020-08-25 16:10:07 +0200726 endpoint_1="{}:{}".format(app_1, endpoint_1),
727 endpoint_2="{}:{}".format(app_2, endpoint_2),
quilesjaae10b42020-01-09 08:49:10 +0000728 )
quilesj29114342019-10-29 09:30:44 +0100729 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400730 message = "Error adding relation between {} and {}: {}".format(
731 ee_id_1, ee_id_2, e
732 )
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100733 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100734 raise N2VCException(message=message)
735
beierlmf52cb7c2020-04-21 16:36:35 -0400736 async def remove_relation(self):
quilesj29114342019-10-29 09:30:44 +0100737 # TODO
beierlmf52cb7c2020-04-21 16:36:35 -0400738 self.log.info("Method not implemented yet")
739 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100740
beierlmf52cb7c2020-04-21 16:36:35 -0400741 async def deregister_execution_environments(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400742 self.log.info("Method not implemented yet")
743 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100744
745 async def delete_namespace(
beierlmf52cb7c2020-04-21 16:36:35 -0400746 self, namespace: str, db_dict: dict = None, total_timeout: float = None
quilesj29114342019-10-29 09:30:44 +0100747 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400748 self.log.info("Deleting namespace={}".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100749
quilesj29114342019-10-29 09:30:44 +0100750 # check arguments
751 if namespace is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400752 raise N2VCBadArgumentsException(
753 message="namespace is mandatory", bad_args=["namespace"]
754 )
quilesj29114342019-10-29 09:30:44 +0100755
beierlmf52cb7c2020-04-21 16:36:35 -0400756 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
757 namespace=namespace
758 )
quilesj29114342019-10-29 09:30:44 +0100759 if ns_id is not None:
quilesj29114342019-10-29 09:30:44 +0100760 try:
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200761 models = await self.libjuju.list_models(contains=ns_id)
762 for model in models:
763 await self.libjuju.destroy_model(
764 model_name=model, total_timeout=total_timeout
765 )
quilesj29114342019-10-29 09:30:44 +0100766 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400767 raise N2VCException(
768 message="Error deleting namespace {} : {}".format(namespace, e)
769 )
quilesj29114342019-10-29 09:30:44 +0100770 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400771 raise N2VCBadArgumentsException(
772 message="only ns_id is permitted to delete yet", bad_args=["namespace"]
773 )
quilesj29114342019-10-29 09:30:44 +0100774
beierlmf52cb7c2020-04-21 16:36:35 -0400775 self.log.info("Namespace {} deleted".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100776
777 async def delete_execution_environment(
aktasd1d55412021-02-21 19:36:20 +0300778 self, ee_id: str, db_dict: dict = None, total_timeout: float = None,
779 scaling_in: bool = False
quilesj29114342019-10-29 09:30:44 +0100780 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400781 self.log.info("Deleting execution environment ee_id={}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100782
quilesj29114342019-10-29 09:30:44 +0100783 # check arguments
784 if ee_id is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400785 raise N2VCBadArgumentsException(
786 message="ee_id is mandatory", bad_args=["ee_id"]
787 )
quilesj29114342019-10-29 09:30:44 +0100788
beierlmf52cb7c2020-04-21 16:36:35 -0400789 model_name, application_name, _machine_id = self._get_ee_id_components(
790 ee_id=ee_id
791 )
quilesj29114342019-10-29 09:30:44 +0100792 try:
aktasd1d55412021-02-21 19:36:20 +0300793 if not scaling_in:
794 # destroy the model
795 # TODO: should this be removed?
796 await self.libjuju.destroy_model(
797 model_name=model_name, total_timeout=total_timeout
798 )
799 else:
800 # get juju model and observer
801 controller = await self.libjuju.get_controller()
802 model = await self.libjuju.get_model(controller, model_name)
803 # destroy the application
804 await self.libjuju.destroy_application(
805 model=model, application_name=application_name)
quilesj29114342019-10-29 09:30:44 +0100806 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400807 raise N2VCException(
808 message=(
809 "Error deleting execution environment {} (application {}) : {}"
810 ).format(ee_id, application_name, e)
811 )
quilesj29114342019-10-29 09:30:44 +0100812
beierlmf52cb7c2020-04-21 16:36:35 -0400813 self.log.info("Execution environment {} deleted".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100814
815 async def exec_primitive(
beierlmf52cb7c2020-04-21 16:36:35 -0400816 self,
817 ee_id: str,
818 primitive_name: str,
819 params_dict: dict,
820 db_dict: dict = None,
821 progress_timeout: float = None,
822 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100823 ) -> str:
824
beierlmf52cb7c2020-04-21 16:36:35 -0400825 self.log.info(
826 "Executing primitive: {} on ee: {}, params: {}".format(
827 primitive_name, ee_id, params_dict
828 )
829 )
quilesj29114342019-10-29 09:30:44 +0100830
quilesj29114342019-10-29 09:30:44 +0100831 # check arguments
832 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400833 raise N2VCBadArgumentsException(
834 message="ee_id is mandatory", bad_args=["ee_id"]
835 )
quilesj29114342019-10-29 09:30:44 +0100836 if primitive_name is None or len(primitive_name) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400837 raise N2VCBadArgumentsException(
838 message="action_name is mandatory", bad_args=["action_name"]
839 )
quilesj29114342019-10-29 09:30:44 +0100840 if params_dict is None:
841 params_dict = dict()
842
843 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400844 (
845 model_name,
846 application_name,
847 _machine_id,
848 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
quilesj29114342019-10-29 09:30:44 +0100849 except Exception:
850 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400851 message="ee_id={} is not a valid execution environment id".format(
852 ee_id
853 ),
854 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100855 )
856
beierlmf52cb7c2020-04-21 16:36:35 -0400857 if primitive_name == "config":
quilesj29114342019-10-29 09:30:44 +0100858 # Special case: config primitive
859 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200860 await self.libjuju.configure_application(
quilesj29114342019-10-29 09:30:44 +0100861 model_name=model_name,
862 application_name=application_name,
863 config=params_dict,
quilesj29114342019-10-29 09:30:44 +0100864 )
David Garcia4fee80e2020-05-13 12:18:38 +0200865 actions = await self.libjuju.get_actions(
866 application_name=application_name, model_name=model_name,
867 )
868 self.log.debug(
869 "Application {} has these actions: {}".format(
870 application_name, actions
871 )
872 )
873 if "verify-ssh-credentials" in actions:
874 # execute verify-credentials
875 num_retries = 20
876 retry_timeout = 15.0
877 for _ in range(num_retries):
878 try:
879 self.log.debug("Executing action verify-ssh-credentials...")
880 output, ok = await self.libjuju.execute_action(
881 model_name=model_name,
882 application_name=application_name,
883 action_name="verify-ssh-credentials",
884 db_dict=db_dict,
885 progress_timeout=progress_timeout,
886 total_timeout=total_timeout,
887 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200888
889 if ok == "failed":
890 self.log.debug(
891 "Error executing verify-ssh-credentials: {}. Retrying..."
892 )
893 await asyncio.sleep(retry_timeout)
894
895 continue
David Garcia4fee80e2020-05-13 12:18:38 +0200896 self.log.debug("Result: {}, output: {}".format(ok, output))
897 break
898 except asyncio.CancelledError:
899 raise
David Garcia4fee80e2020-05-13 12:18:38 +0200900 else:
901 self.log.error(
902 "Error executing verify-ssh-credentials after {} retries. ".format(
903 num_retries
904 )
905 )
906 else:
907 msg = "Action verify-ssh-credentials does not exist in application {}".format(
908 application_name
909 )
910 self.log.debug(msg=msg)
quilesj29114342019-10-29 09:30:44 +0100911 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400912 self.log.error("Error configuring juju application: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100913 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400914 message="Error configuring application into ee={} : {}".format(
915 ee_id, e
916 ),
917 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100918 )
beierlmf52cb7c2020-04-21 16:36:35 -0400919 return "CONFIG OK"
quilesj29114342019-10-29 09:30:44 +0100920 else:
921 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200922 output, status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100923 model_name=model_name,
924 application_name=application_name,
925 action_name=primitive_name,
926 db_dict=db_dict,
927 progress_timeout=progress_timeout,
928 total_timeout=total_timeout,
929 **params_dict
930 )
beierlmf52cb7c2020-04-21 16:36:35 -0400931 if status == "completed":
quilesj29114342019-10-29 09:30:44 +0100932 return output
933 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400934 raise Exception("status is not completed: {}".format(status))
quilesj29114342019-10-29 09:30:44 +0100935 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400936 self.log.error(
937 "Error executing primitive {}: {}".format(primitive_name, e)
938 )
quilesj29114342019-10-29 09:30:44 +0100939 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400940 message="Error executing primitive {} into ee={} : {}".format(
941 primitive_name, ee_id, e
942 ),
943 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100944 )
945
946 async def disconnect(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400947 self.log.info("closing juju N2VC...")
David Garcia4fee80e2020-05-13 12:18:38 +0200948 try:
949 await self.libjuju.disconnect()
950 except Exception as e:
951 raise N2VCConnectionException(
952 message="Error disconnecting controller: {}".format(e), url=self.url
953 )
quilesj29114342019-10-29 09:30:44 +0100954
955 """
David Garcia4fee80e2020-05-13 12:18:38 +0200956####################################################################################
957################################### P R I V A T E ##################################
958####################################################################################
quilesj29114342019-10-29 09:30:44 +0100959 """
960
beierlmf52cb7c2020-04-21 16:36:35 -0400961 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +0100962
963 # write ee_id to database: _admin.deployed.VCA.x
964 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400965 the_table = db_dict["collection"]
966 the_filter = db_dict["filter"]
967 the_path = db_dict["path"]
968 if not the_path[-1] == ".":
969 the_path = the_path + "."
970 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100971 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +0100972 self.db.set_one(
973 table=the_table,
974 q_filter=the_filter,
975 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400976 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +0100977 )
tierno8ff11992020-03-26 09:51:11 +0000978 except asyncio.CancelledError:
979 raise
quilesj29114342019-10-29 09:30:44 +0100980 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400981 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100982
983 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -0400984 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +0100985 """
986 Build an execution environment id form model, application and machine
987 :param model_name:
988 :param application_name:
989 :param machine_id:
990 :return:
991 """
992 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -0400993 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +0100994
995 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -0400996 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +0100997 """
998 Get model, application and machine components from an execution environment id
999 :param ee_id:
1000 :return: model_name, application_name, machine_id
1001 """
1002
1003 if ee_id is None:
1004 return None, None, None
1005
1006 # split components of id
beierlmf52cb7c2020-04-21 16:36:35 -04001007 parts = ee_id.split(".")
quilesj29114342019-10-29 09:30:44 +01001008 model_name = parts[0]
1009 application_name = parts[1]
1010 machine_id = parts[2]
1011 return model_name, application_name, machine_id
1012
1013 def _get_application_name(self, namespace: str) -> str:
1014 """
1015 Build application name from namespace
1016 :param namespace:
1017 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1018 """
1019
Adam Israel18046072019-12-08 21:44:29 -05001020 # TODO: Enforce the Juju 50-character application limit
1021
quilesj29114342019-10-29 09:30:44 +01001022 # split namespace components
beierlmf52cb7c2020-04-21 16:36:35 -04001023 _, _, vnf_id, vdu_id, vdu_count = self._get_namespace_components(
1024 namespace=namespace
1025 )
quilesj29114342019-10-29 09:30:44 +01001026
1027 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001028 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001029 else:
Adam Israel18046072019-12-08 21:44:29 -05001030 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001031 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001032
1033 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001034 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001035 else:
Adam Israel18046072019-12-08 21:44:29 -05001036 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001037 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001038
1039 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001040 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001041 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001042 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001043
beierlmf52cb7c2020-04-21 16:36:35 -04001044 application_name = "app-{}{}{}".format(vnf_id, vdu_id, vdu_count)
quilesj29114342019-10-29 09:30:44 +01001045
1046 return N2VCJujuConnector._format_app_name(application_name)
1047
quilesj29114342019-10-29 09:30:44 +01001048 def _create_juju_public_key(self):
1049 """Recreate the Juju public key on lcm container, if needed
1050 Certain libjuju commands expect to be run from the same machine as Juju
1051 is bootstrapped to. This method will write the public key to disk in
1052 that location: ~/.local/share/juju/ssh/juju_id_rsa.pub
1053 """
1054
1055 # Make sure that we have a public key before writing to disk
1056 if self.public_key is None or len(self.public_key) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001057 if "OSMLCM_VCA_PUBKEY" in os.environ:
1058 self.public_key = os.getenv("OSMLCM_VCA_PUBKEY", "")
quilesj29114342019-10-29 09:30:44 +01001059 if len(self.public_key) == 0:
1060 return
1061 else:
1062 return
1063
beierlmf52cb7c2020-04-21 16:36:35 -04001064 pk_path = "{}/.local/share/juju/ssh".format(os.path.expanduser("~"))
quilesj29114342019-10-29 09:30:44 +01001065 file_path = "{}/juju_id_rsa.pub".format(pk_path)
beierlmf52cb7c2020-04-21 16:36:35 -04001066 self.log.debug(
1067 "writing juju public key to file:\n{}\npublic key: {}".format(
1068 file_path, self.public_key
1069 )
1070 )
quilesj29114342019-10-29 09:30:44 +01001071 if not os.path.exists(pk_path):
1072 # create path and write file
1073 os.makedirs(pk_path)
beierlmf52cb7c2020-04-21 16:36:35 -04001074 with open(file_path, "w") as f:
1075 self.log.debug("Creating juju public key file: {}".format(file_path))
quilesj29114342019-10-29 09:30:44 +01001076 f.write(self.public_key)
1077 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001078 self.log.debug("juju public key file already exists: {}".format(file_path))
quilesj29114342019-10-29 09:30:44 +01001079
1080 @staticmethod
1081 def _format_model_name(name: str) -> str:
1082 """Format the name of the model.
1083
1084 Model names may only contain lowercase letters, digits and hyphens
1085 """
1086
beierlmf52cb7c2020-04-21 16:36:35 -04001087 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001088
1089 @staticmethod
1090 def _format_app_name(name: str) -> str:
1091 """Format the name of the application (in order to assure valid application name).
1092
1093 Application names have restrictions (run juju deploy --help):
1094 - contains lowercase letters 'a'-'z'
1095 - contains numbers '0'-'9'
1096 - contains hyphens '-'
1097 - starts with a lowercase letter
1098 - not two or more consecutive hyphens
1099 - after a hyphen, not a group with all numbers
1100 """
1101
1102 def all_numbers(s: str) -> bool:
1103 for c in s:
1104 if not c.isdigit():
1105 return False
1106 return True
1107
beierlmf52cb7c2020-04-21 16:36:35 -04001108 new_name = name.replace("_", "-")
1109 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001110 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001111 while new_name.find("--") >= 0:
1112 new_name = new_name.replace("--", "-")
1113 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001114
1115 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001116 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001117 for i in range(len(groups)):
1118 group = groups[i]
1119 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001120 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001121 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001122 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001123 app_name += group
1124
1125 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001126 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001127
1128 return app_name