blob: fff78c94414d5866f31945e1076c552c4eb60b07 [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
quilesj29114342019-10-29 09:30:44 +010024import base64
25import binascii
beierlmf52cb7c2020-04-21 16:36:35 -040026import logging
27import os
quilesj29114342019-10-29 09:30:44 +010028import re
29
beierlmf52cb7c2020-04-21 16:36:35 -040030from n2vc.exceptions import (
31 N2VCBadArgumentsException,
32 N2VCException,
33 N2VCConnectionException,
34 N2VCExecutionException,
35 N2VCInvalidCertificate,
almagiaba6e5322020-09-16 09:44:40 +020036 # N2VCNotFound,
beierlmf52cb7c2020-04-21 16:36:35 -040037 MethodNotImplemented,
Dominik Fleischmannb9513342020-06-09 11:57:14 +020038 JujuK8sProxycharmNotSupported,
beierlmf52cb7c2020-04-21 16:36:35 -040039)
quilesj29114342019-10-29 09:30:44 +010040from n2vc.n2vc_conn import N2VCConnector
quilesj776ab392019-12-12 16:10:54 +000041from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
David Garcia4fee80e2020-05-13 12:18:38 +020042from n2vc.libjuju import Libjuju
quilesj29114342019-10-29 09:30:44 +010043
44
45class N2VCJujuConnector(N2VCConnector):
46
47 """
David Garcia4fee80e2020-05-13 12:18:38 +020048####################################################################################
49################################### P U B L I C ####################################
50####################################################################################
quilesj29114342019-10-29 09:30:44 +010051 """
52
David Garcia347aae62020-04-29 12:34:23 +020053 BUILT_IN_CLOUDS = ["localhost", "microk8s"]
54
quilesj29114342019-10-29 09:30:44 +010055 def __init__(
56 self,
57 db: object,
58 fs: object,
59 log: object = None,
60 loop: object = None,
beierlmf52cb7c2020-04-21 16:36:35 -040061 url: str = "127.0.0.1:17070",
62 username: str = "admin",
quilesj29114342019-10-29 09:30:44 +010063 vca_config: dict = None,
beierlmf52cb7c2020-04-21 16:36:35 -040064 on_update_db=None,
quilesj29114342019-10-29 09:30:44 +010065 ):
66 """Initialize juju N2VC connector
67 """
68
69 # parent class constructor
70 N2VCConnector.__init__(
71 self,
72 db=db,
73 fs=fs,
74 log=log,
75 loop=loop,
76 url=url,
77 username=username,
78 vca_config=vca_config,
beierlmf52cb7c2020-04-21 16:36:35 -040079 on_update_db=on_update_db,
quilesj29114342019-10-29 09:30:44 +010080 )
81
82 # silence websocket traffic log
beierlmf52cb7c2020-04-21 16:36:35 -040083 logging.getLogger("websockets.protocol").setLevel(logging.INFO)
84 logging.getLogger("juju.client.connection").setLevel(logging.WARN)
85 logging.getLogger("model").setLevel(logging.WARN)
quilesj29114342019-10-29 09:30:44 +010086
beierlmf52cb7c2020-04-21 16:36:35 -040087 self.log.info("Initializing N2VC juju connector...")
quilesj29114342019-10-29 09:30:44 +010088
89 """
90 ##############################################################
91 # check arguments
92 ##############################################################
93 """
94
95 # juju URL
96 if url is None:
beierlmf52cb7c2020-04-21 16:36:35 -040097 raise N2VCBadArgumentsException("Argument url is mandatory", ["url"])
98 url_parts = url.split(":")
quilesj29114342019-10-29 09:30:44 +010099 if len(url_parts) != 2:
beierlmf52cb7c2020-04-21 16:36:35 -0400100 raise N2VCBadArgumentsException(
101 "Argument url: bad format (localhost:port) -> {}".format(url), ["url"]
102 )
quilesj29114342019-10-29 09:30:44 +0100103 self.hostname = url_parts[0]
104 try:
105 self.port = int(url_parts[1])
106 except ValueError:
beierlmf52cb7c2020-04-21 16:36:35 -0400107 raise N2VCBadArgumentsException(
108 "url port must be a number -> {}".format(url), ["url"]
109 )
quilesj29114342019-10-29 09:30:44 +0100110
111 # juju USERNAME
112 if username is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400113 raise N2VCBadArgumentsException(
114 "Argument username is mandatory", ["username"]
115 )
quilesj29114342019-10-29 09:30:44 +0100116
117 # juju CONFIGURATION
118 if vca_config is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400119 raise N2VCBadArgumentsException(
120 "Argument vca_config is mandatory", ["vca_config"]
121 )
quilesj29114342019-10-29 09:30:44 +0100122
beierlmf52cb7c2020-04-21 16:36:35 -0400123 if "secret" in vca_config:
124 self.secret = vca_config["secret"]
quilesj29114342019-10-29 09:30:44 +0100125 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400126 raise N2VCBadArgumentsException(
127 "Argument vca_config.secret is mandatory", ["vca_config.secret"]
128 )
quilesj29114342019-10-29 09:30:44 +0100129
130 # pubkey of juju client in osm machine: ~/.local/share/juju/ssh/juju_id_rsa.pub
131 # if exists, it will be written in lcm container: _create_juju_public_key()
beierlmf52cb7c2020-04-21 16:36:35 -0400132 if "public_key" in vca_config:
133 self.public_key = vca_config["public_key"]
quilesj29114342019-10-29 09:30:44 +0100134 else:
135 self.public_key = None
136
137 # TODO: Verify ca_cert is valid before using. VCA will crash
138 # if the ca_cert isn't formatted correctly.
139 def base64_to_cacert(b64string):
140 """Convert the base64-encoded string containing the VCA CACERT.
141
142 The input string....
143
144 """
145 try:
146 cacert = base64.b64decode(b64string).decode("utf-8")
147
beierlmf52cb7c2020-04-21 16:36:35 -0400148 cacert = re.sub(r"\\n", r"\n", cacert,)
quilesj29114342019-10-29 09:30:44 +0100149 except binascii.Error as e:
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100150 self.log.debug("Caught binascii.Error: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100151 raise N2VCInvalidCertificate(message="Invalid CA Certificate")
152
153 return cacert
154
beierlmf52cb7c2020-04-21 16:36:35 -0400155 self.ca_cert = vca_config.get("ca_cert")
quilesj29114342019-10-29 09:30:44 +0100156 if self.ca_cert:
beierlmf52cb7c2020-04-21 16:36:35 -0400157 self.ca_cert = base64_to_cacert(vca_config["ca_cert"])
quilesj29114342019-10-29 09:30:44 +0100158
David Garcia81045962020-07-16 12:37:13 +0200159 if "api_proxy" in vca_config and vca_config["api_proxy"] != "":
beierlmf52cb7c2020-04-21 16:36:35 -0400160 self.api_proxy = vca_config["api_proxy"]
161 self.log.debug(
162 "api_proxy for native charms configured: {}".format(self.api_proxy)
163 )
quilesj29114342019-10-29 09:30:44 +0100164 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400165 self.warning(
David Garcia81045962020-07-16 12:37:13 +0200166 "api_proxy is not configured"
beierlmf52cb7c2020-04-21 16:36:35 -0400167 )
tiernoec8a5042020-06-24 13:57:10 +0000168 self.api_proxy = None
quilesj29114342019-10-29 09:30:44 +0100169
beierlmf52cb7c2020-04-21 16:36:35 -0400170 if "enable_os_upgrade" in vca_config:
171 self.enable_os_upgrade = vca_config["enable_os_upgrade"]
garciadeblas923510c2019-12-17 15:02:11 +0100172 else:
173 self.enable_os_upgrade = True
174
beierlmf52cb7c2020-04-21 16:36:35 -0400175 if "apt_mirror" in vca_config:
176 self.apt_mirror = vca_config["apt_mirror"]
garciadeblas923510c2019-12-17 15:02:11 +0100177 else:
178 self.apt_mirror = None
179
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200180 self.cloud = vca_config.get('cloud')
181 self.k8s_cloud = None
182 if "k8s_cloud" in vca_config:
183 self.k8s_cloud = vca_config.get("k8s_cloud")
184 self.log.debug('Arguments have been checked')
quilesj29114342019-10-29 09:30:44 +0100185
186 # juju data
beierlmf52cb7c2020-04-21 16:36:35 -0400187 self.controller = None # it will be filled when connect to juju
188 self.juju_models = {} # model objects for every model_name
189 self.juju_observers = {} # model observers for every model_name
190 self._connecting = (
191 False # while connecting to juju (to avoid duplicate connections)
192 )
193 self._authenticated = (
194 False # it will be True when juju connection be stablished
195 )
196 self._creating_model = False # True during model creation
David Garcia4fee80e2020-05-13 12:18:38 +0200197 self.libjuju = Libjuju(
198 endpoint=self.url,
199 api_proxy=self.api_proxy,
200 enable_os_upgrade=self.enable_os_upgrade,
201 apt_mirror=self.apt_mirror,
202 username=self.username,
203 password=self.secret,
204 cacert=self.ca_cert,
205 loop=self.loop,
206 log=self.log,
207 db=self.db,
208 n2vc=self,
209 )
quilesj29114342019-10-29 09:30:44 +0100210
beierlmf52cb7c2020-04-21 16:36:35 -0400211 # create juju pub key file in lcm container at
212 # ./local/share/juju/ssh/juju_id_rsa.pub
quilesj29114342019-10-29 09:30:44 +0100213 self._create_juju_public_key()
214
beierlmf52cb7c2020-04-21 16:36:35 -0400215 self.log.info("N2VC juju connector initialized")
quilesj29114342019-10-29 09:30:44 +0100216
quilesj776ab392019-12-12 16:10:54 +0000217 async def get_status(self, namespace: str, yaml_format: bool = True):
218
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100219 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
quilesj29114342019-10-29 09:30:44 +0100220
beierlmf52cb7c2020-04-21 16:36:35 -0400221 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
222 namespace=namespace
223 )
quilesj29114342019-10-29 09:30:44 +0100224 # model name is ns_id
225 model_name = ns_id
226 if model_name is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400227 msg = "Namespace {} not valid".format(namespace)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100228 self.log.error(msg)
beierlmf52cb7c2020-04-21 16:36:35 -0400229 raise N2VCBadArgumentsException(msg, ["namespace"])
quilesj29114342019-10-29 09:30:44 +0100230
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200231 status = {}
232 models = await self.libjuju.list_models(contains=ns_id)
233
234 for m in models:
David Garciad745e222020-06-30 08:39:26 +0200235 status[m] = await self.libjuju.get_model_status(m)
quilesj29114342019-10-29 09:30:44 +0100236
quilesj776ab392019-12-12 16:10:54 +0000237 if yaml_format:
238 return obj_to_yaml(status)
239 else:
240 return obj_to_dict(status)
quilesj29114342019-10-29 09:30:44 +0100241
242 async def create_execution_environment(
243 self,
244 namespace: str,
245 db_dict: dict,
246 reuse_ee_id: str = None,
247 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400248 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100249 ) -> (str, dict):
250
beierlmf52cb7c2020-04-21 16:36:35 -0400251 self.log.info(
252 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
253 namespace, reuse_ee_id
254 )
255 )
quilesj29114342019-10-29 09:30:44 +0100256
quilesj29114342019-10-29 09:30:44 +0100257 machine_id = None
258 if reuse_ee_id:
beierlmf52cb7c2020-04-21 16:36:35 -0400259 model_name, application_name, machine_id = self._get_ee_id_components(
260 ee_id=reuse_ee_id
261 )
quilesj29114342019-10-29 09:30:44 +0100262 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400263 (
264 _nsi_id,
265 ns_id,
266 _vnf_id,
267 _vdu_id,
268 _vdu_count,
269 ) = self._get_namespace_components(namespace=namespace)
quilesj29114342019-10-29 09:30:44 +0100270 # model name is ns_id
271 model_name = ns_id
272 # application name
273 application_name = self._get_application_name(namespace=namespace)
274
beierlmf52cb7c2020-04-21 16:36:35 -0400275 self.log.debug(
276 "model name: {}, application name: {}, machine_id: {}".format(
277 model_name, application_name, machine_id
278 )
279 )
quilesj29114342019-10-29 09:30:44 +0100280
281 # create or reuse a new juju machine
282 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200283 if not await self.libjuju.model_exists(model_name):
284 await self.libjuju.add_model(model_name, cloud_name=self.cloud)
285 machine, new = await self.libjuju.create_machine(
quilesj29114342019-10-29 09:30:44 +0100286 model_name=model_name,
quilesj29114342019-10-29 09:30:44 +0100287 machine_id=machine_id,
288 db_dict=db_dict,
289 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400290 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100291 )
David Garcia4fee80e2020-05-13 12:18:38 +0200292 # id for the execution environment
293 ee_id = N2VCJujuConnector._build_ee_id(
294 model_name=model_name,
295 application_name=application_name,
296 machine_id=str(machine.entity_id),
297 )
298 self.log.debug("ee_id: {}".format(ee_id))
299
300 if new:
301 # write ee_id in database
302 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
303
quilesj29114342019-10-29 09:30:44 +0100304 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400305 message = "Error creating machine on juju: {}".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100306 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100307 raise N2VCException(message=message)
308
quilesj29114342019-10-29 09:30:44 +0100309 # new machine credentials
David Garcia4fee80e2020-05-13 12:18:38 +0200310 credentials = {
311 "hostname": machine.dns_name,
312 }
quilesj29114342019-10-29 09:30:44 +0100313
beierlmf52cb7c2020-04-21 16:36:35 -0400314 self.log.info(
315 "Execution environment created. ee_id: {}, credentials: {}".format(
316 ee_id, credentials
317 )
318 )
quilesj29114342019-10-29 09:30:44 +0100319
320 return ee_id, credentials
321
322 async def register_execution_environment(
323 self,
324 namespace: str,
325 credentials: dict,
326 db_dict: dict,
327 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400328 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100329 ) -> str:
330
beierlmf52cb7c2020-04-21 16:36:35 -0400331 self.log.info(
332 "Registering execution environment. namespace={}, credentials={}".format(
333 namespace, credentials
334 )
335 )
quilesj29114342019-10-29 09:30:44 +0100336
337 if credentials is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400338 raise N2VCBadArgumentsException(
339 message="credentials are mandatory", bad_args=["credentials"]
340 )
341 if credentials.get("hostname"):
342 hostname = credentials["hostname"]
quilesj29114342019-10-29 09:30:44 +0100343 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400344 raise N2VCBadArgumentsException(
345 message="hostname is mandatory", bad_args=["credentials.hostname"]
346 )
347 if credentials.get("username"):
348 username = credentials["username"]
quilesj29114342019-10-29 09:30:44 +0100349 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400350 raise N2VCBadArgumentsException(
351 message="username is mandatory", bad_args=["credentials.username"]
352 )
353 if "private_key_path" in credentials:
354 private_key_path = credentials["private_key_path"]
quilesj29114342019-10-29 09:30:44 +0100355 else:
356 # if not passed as argument, use generated private key path
357 private_key_path = self.private_key_path
358
beierlmf52cb7c2020-04-21 16:36:35 -0400359 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
360 namespace=namespace
361 )
quilesj29114342019-10-29 09:30:44 +0100362
363 # model name
364 model_name = ns_id
365 # application name
366 application_name = self._get_application_name(namespace=namespace)
367
368 # register machine on juju
369 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200370 if not await self.libjuju.model_exists(model_name):
371 await self.libjuju.add_model(model_name, cloud_name=self.cloud)
372 machine_id = await self.libjuju.provision_machine(
quilesj29114342019-10-29 09:30:44 +0100373 model_name=model_name,
374 hostname=hostname,
375 username=username,
376 private_key_path=private_key_path,
377 db_dict=db_dict,
378 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400379 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100380 )
381 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400382 self.log.error("Error registering machine: {}".format(e))
383 raise N2VCException(
384 message="Error registering machine on juju: {}".format(e)
385 )
quilesjac4e0de2019-11-27 16:12:02 +0000386
beierlmf52cb7c2020-04-21 16:36:35 -0400387 self.log.info("Machine registered: {}".format(machine_id))
quilesj29114342019-10-29 09:30:44 +0100388
389 # id for the execution environment
390 ee_id = N2VCJujuConnector._build_ee_id(
391 model_name=model_name,
392 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400393 machine_id=str(machine_id),
quilesj29114342019-10-29 09:30:44 +0100394 )
395
beierlmf52cb7c2020-04-21 16:36:35 -0400396 self.log.info("Execution environment registered. ee_id: {}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100397
398 return ee_id
399
400 async def install_configuration_sw(
401 self,
402 ee_id: str,
403 artifact_path: str,
404 db_dict: dict,
405 progress_timeout: float = None,
David Garciadfaa6e82020-04-01 16:06:39 +0200406 total_timeout: float = None,
407 config: dict = None,
David Garciaf8a9d462020-03-25 18:19:02 +0100408 num_units: int = 1,
quilesj29114342019-10-29 09:30:44 +0100409 ):
410
beierlmf52cb7c2020-04-21 16:36:35 -0400411 self.log.info(
412 (
413 "Installing configuration sw on ee_id: {}, "
414 "artifact path: {}, db_dict: {}"
415 ).format(ee_id, artifact_path, db_dict)
416 )
quilesj29114342019-10-29 09:30:44 +0100417
quilesj29114342019-10-29 09:30:44 +0100418 # check arguments
419 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400420 raise N2VCBadArgumentsException(
421 message="ee_id is mandatory", bad_args=["ee_id"]
422 )
quilesj29114342019-10-29 09:30:44 +0100423 if artifact_path is None or len(artifact_path) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400424 raise N2VCBadArgumentsException(
425 message="artifact_path is mandatory", bad_args=["artifact_path"]
426 )
quilesj29114342019-10-29 09:30:44 +0100427 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400428 raise N2VCBadArgumentsException(
429 message="db_dict is mandatory", bad_args=["db_dict"]
430 )
quilesj29114342019-10-29 09:30:44 +0100431
432 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400433 (
434 model_name,
435 application_name,
436 machine_id,
437 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
438 self.log.debug(
439 "model: {}, application: {}, machine: {}".format(
440 model_name, application_name, machine_id
441 )
442 )
443 except Exception:
quilesj29114342019-10-29 09:30:44 +0100444 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400445 message="ee_id={} is not a valid execution environment id".format(
446 ee_id
447 ),
448 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100449 )
450
451 # remove // in charm path
beierlmf52cb7c2020-04-21 16:36:35 -0400452 while artifact_path.find("//") >= 0:
453 artifact_path = artifact_path.replace("//", "/")
quilesj29114342019-10-29 09:30:44 +0100454
455 # check charm path
456 if not self.fs.file_exists(artifact_path, mode="dir"):
beierlmf52cb7c2020-04-21 16:36:35 -0400457 msg = "artifact path does not exist: {}".format(artifact_path)
458 raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"])
quilesj29114342019-10-29 09:30:44 +0100459
beierlmf52cb7c2020-04-21 16:36:35 -0400460 if artifact_path.startswith("/"):
quilesj29114342019-10-29 09:30:44 +0100461 full_path = self.fs.path + artifact_path
462 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400463 full_path = self.fs.path + "/" + artifact_path
quilesj29114342019-10-29 09:30:44 +0100464
465 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200466 await self.libjuju.deploy_charm(
quilesj29114342019-10-29 09:30:44 +0100467 model_name=model_name,
468 application_name=application_name,
David Garcia4fee80e2020-05-13 12:18:38 +0200469 path=full_path,
quilesj29114342019-10-29 09:30:44 +0100470 machine_id=machine_id,
471 db_dict=db_dict,
472 progress_timeout=progress_timeout,
David Garciadfaa6e82020-04-01 16:06:39 +0200473 total_timeout=total_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400474 config=config,
David Garciaf8a9d462020-03-25 18:19:02 +0100475 num_units=num_units,
quilesj29114342019-10-29 09:30:44 +0100476 )
477 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400478 raise N2VCException(
479 message="Error desploying charm into ee={} : {}".format(ee_id, e)
480 )
quilesj29114342019-10-29 09:30:44 +0100481
beierlmf52cb7c2020-04-21 16:36:35 -0400482 self.log.info("Configuration sw installed")
quilesj29114342019-10-29 09:30:44 +0100483
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200484 async def install_k8s_proxy_charm(
485 self,
486 charm_name: str,
487 namespace: str,
488 artifact_path: str,
489 db_dict: dict,
490 progress_timeout: float = None,
491 total_timeout: float = None,
492 config: dict = None,
493 ) -> str:
494 """
495 Install a k8s proxy charm
496
497 :param charm_name: Name of the charm being deployed
498 :param namespace: collection of all the uuids related to the charm.
499 :param str artifact_path: where to locate the artifacts (parent folder) using
500 the self.fs
501 the final artifact path will be a combination of this artifact_path and
502 additional string from the config_dict (e.g. charm name)
503 :param dict db_dict: where to write into database when the status changes.
504 It contains a dict with
505 {collection: <str>, filter: {}, path: <str>},
506 e.g. {collection: "nsrs", filter:
507 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
508 :param float progress_timeout:
509 :param float total_timeout:
510 :param config: Dictionary with additional configuration
511
512 :returns ee_id: execution environment id.
513 """
514 self.log.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
515 .format(charm_name, artifact_path, db_dict))
516
517 if not self.k8s_cloud:
518 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
519
520 if artifact_path is None or len(artifact_path) == 0:
521 raise N2VCBadArgumentsException(
522 message="artifact_path is mandatory", bad_args=["artifact_path"]
523 )
524 if db_dict is None:
525 raise N2VCBadArgumentsException(message='db_dict is mandatory', bad_args=['db_dict'])
526
527 # remove // in charm path
528 while artifact_path.find('//') >= 0:
529 artifact_path = artifact_path.replace('//', '/')
530
531 # check charm path
532 if not self.fs.file_exists(artifact_path, mode="dir"):
533 msg = 'artifact path does not exist: {}'.format(artifact_path)
534 raise N2VCBadArgumentsException(message=msg, bad_args=['artifact_path'])
535
536 if artifact_path.startswith('/'):
537 full_path = self.fs.path + artifact_path
538 else:
539 full_path = self.fs.path + '/' + artifact_path
540
541 _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace)
542 model_name = '{}-k8s'.format(ns_id)
543
544 await self.libjuju.add_model(model_name, self.k8s_cloud)
545 application_name = self._get_application_name(namespace)
546
547 try:
548 await self.libjuju.deploy_charm(
549 model_name=model_name,
550 application_name=application_name,
551 path=full_path,
552 machine_id=None,
553 db_dict=db_dict,
554 progress_timeout=progress_timeout,
555 total_timeout=total_timeout,
556 config=config
557 )
558 except Exception as e:
559 raise N2VCException(message='Error deploying charm: {}'.format(e))
560
561 self.log.info('K8s proxy charm installed')
562 ee_id = N2VCJujuConnector._build_ee_id(
563 model_name=model_name,
564 application_name=application_name,
565 machine_id="k8s",
566 )
Dominik Fleischmann7ace6fa2020-06-29 16:16:28 +0200567
568 self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
569
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200570 return ee_id
571
quilesj29114342019-10-29 09:30:44 +0100572 async def get_ee_ssh_public__key(
573 self,
574 ee_id: str,
575 db_dict: dict,
576 progress_timeout: float = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400577 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100578 ) -> str:
579
beierlmf52cb7c2020-04-21 16:36:35 -0400580 self.log.info(
581 (
582 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
583 ).format(ee_id, db_dict)
584 )
quilesj29114342019-10-29 09:30:44 +0100585
quilesj29114342019-10-29 09:30:44 +0100586 # check arguments
587 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400588 raise N2VCBadArgumentsException(
589 message="ee_id is mandatory", bad_args=["ee_id"]
590 )
quilesj29114342019-10-29 09:30:44 +0100591 if db_dict is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400592 raise N2VCBadArgumentsException(
593 message="db_dict is mandatory", bad_args=["db_dict"]
594 )
quilesj29114342019-10-29 09:30:44 +0100595
596 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400597 (
598 model_name,
599 application_name,
600 machine_id,
601 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
602 self.log.debug(
603 "model: {}, application: {}, machine: {}".format(
604 model_name, application_name, machine_id
605 )
606 )
607 except Exception:
quilesj29114342019-10-29 09:30:44 +0100608 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400609 message="ee_id={} is not a valid execution environment id".format(
610 ee_id
611 ),
612 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100613 )
614
615 # try to execute ssh layer primitives (if exist):
616 # generate-ssh-key
617 # get-ssh-public-key
618
619 output = None
620
David Garcia4fee80e2020-05-13 12:18:38 +0200621 application_name = N2VCJujuConnector._format_app_name(application_name)
622
quilesj29114342019-10-29 09:30:44 +0100623 # execute action: generate-ssh-key
624 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200625 output, _status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100626 model_name=model_name,
627 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400628 action_name="generate-ssh-key",
quilesj29114342019-10-29 09:30:44 +0100629 db_dict=db_dict,
630 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400631 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100632 )
633 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400634 self.log.info(
635 "Skipping exception while executing action generate-ssh-key: {}".format(
636 e
637 )
638 )
quilesj29114342019-10-29 09:30:44 +0100639
640 # execute action: get-ssh-public-key
641 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200642 output, _status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100643 model_name=model_name,
644 application_name=application_name,
beierlmf52cb7c2020-04-21 16:36:35 -0400645 action_name="get-ssh-public-key",
quilesj29114342019-10-29 09:30:44 +0100646 db_dict=db_dict,
647 progress_timeout=progress_timeout,
beierlmf52cb7c2020-04-21 16:36:35 -0400648 total_timeout=total_timeout,
quilesj29114342019-10-29 09:30:44 +0100649 )
650 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400651 msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100652 self.log.info(msg)
David Garcia4fee80e2020-05-13 12:18:38 +0200653 raise N2VCExecutionException(e, primitive_name="get-ssh-public-key")
quilesj29114342019-10-29 09:30:44 +0100654
655 # return public key if exists
David Garcia9ae8fa52019-12-09 18:50:03 +0100656 return output["pubkey"] if "pubkey" in output else output
quilesj29114342019-10-29 09:30:44 +0100657
658 async def add_relation(
beierlmf52cb7c2020-04-21 16:36:35 -0400659 self, ee_id_1: str, ee_id_2: str, endpoint_1: str, endpoint_2: str
quilesj29114342019-10-29 09:30:44 +0100660 ):
661
beierlmf52cb7c2020-04-21 16:36:35 -0400662 self.log.debug(
663 "adding new relation between {} and {}, endpoints: {}, {}".format(
664 ee_id_1, ee_id_2, endpoint_1, endpoint_2
665 )
666 )
quilesj29114342019-10-29 09:30:44 +0100667
quilesjaae10b42020-01-09 08:49:10 +0000668 # check arguments
669 if not ee_id_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400670 message = "EE 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100671 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400672 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_1"])
quilesjaae10b42020-01-09 08:49:10 +0000673 if not ee_id_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400674 message = "EE 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100675 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400676 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_2"])
quilesjaae10b42020-01-09 08:49:10 +0000677 if not endpoint_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400678 message = "endpoint 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100679 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400680 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_1"])
quilesjaae10b42020-01-09 08:49:10 +0000681 if not endpoint_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400682 message = "endpoint 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100683 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400684 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"])
quilesjaae10b42020-01-09 08:49:10 +0000685
quilesjaae10b42020-01-09 08:49:10 +0000686 # get the model, the applications and the machines from the ee_id's
beierlmf52cb7c2020-04-21 16:36:35 -0400687 model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1)
688 model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2)
quilesj29114342019-10-29 09:30:44 +0100689
690 # model must be the same
691 if model_1 != model_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400692 message = "EE models are not the same: {} vs {}".format(ee_id_1, ee_id_2)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100693 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400694 raise N2VCBadArgumentsException(
695 message=message, bad_args=["ee_id_1", "ee_id_2"]
696 )
quilesj29114342019-10-29 09:30:44 +0100697
698 # add juju relations between two applications
699 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200700 await self.libjuju.add_relation(
quilesjaae10b42020-01-09 08:49:10 +0000701 model_name=model_1,
David Garcia8331f7c2020-08-25 16:10:07 +0200702 endpoint_1="{}:{}".format(app_1, endpoint_1),
703 endpoint_2="{}:{}".format(app_2, endpoint_2),
quilesjaae10b42020-01-09 08:49:10 +0000704 )
quilesj29114342019-10-29 09:30:44 +0100705 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400706 message = "Error adding relation between {} and {}: {}".format(
707 ee_id_1, ee_id_2, e
708 )
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100709 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100710 raise N2VCException(message=message)
711
beierlmf52cb7c2020-04-21 16:36:35 -0400712 async def remove_relation(self):
quilesj29114342019-10-29 09:30:44 +0100713 # TODO
beierlmf52cb7c2020-04-21 16:36:35 -0400714 self.log.info("Method not implemented yet")
715 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100716
beierlmf52cb7c2020-04-21 16:36:35 -0400717 async def deregister_execution_environments(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400718 self.log.info("Method not implemented yet")
719 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100720
721 async def delete_namespace(
beierlmf52cb7c2020-04-21 16:36:35 -0400722 self, namespace: str, db_dict: dict = None, total_timeout: float = None
quilesj29114342019-10-29 09:30:44 +0100723 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400724 self.log.info("Deleting namespace={}".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100725
quilesj29114342019-10-29 09:30:44 +0100726 # check arguments
727 if namespace is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400728 raise N2VCBadArgumentsException(
729 message="namespace is mandatory", bad_args=["namespace"]
730 )
quilesj29114342019-10-29 09:30:44 +0100731
beierlmf52cb7c2020-04-21 16:36:35 -0400732 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
733 namespace=namespace
734 )
quilesj29114342019-10-29 09:30:44 +0100735 if ns_id is not None:
quilesj29114342019-10-29 09:30:44 +0100736 try:
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200737 models = await self.libjuju.list_models(contains=ns_id)
738 for model in models:
739 await self.libjuju.destroy_model(
740 model_name=model, total_timeout=total_timeout
741 )
quilesj29114342019-10-29 09:30:44 +0100742 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400743 raise N2VCException(
744 message="Error deleting namespace {} : {}".format(namespace, e)
745 )
quilesj29114342019-10-29 09:30:44 +0100746 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400747 raise N2VCBadArgumentsException(
748 message="only ns_id is permitted to delete yet", bad_args=["namespace"]
749 )
quilesj29114342019-10-29 09:30:44 +0100750
beierlmf52cb7c2020-04-21 16:36:35 -0400751 self.log.info("Namespace {} deleted".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100752
753 async def delete_execution_environment(
beierlmf52cb7c2020-04-21 16:36:35 -0400754 self, ee_id: str, db_dict: dict = None, total_timeout: float = None
quilesj29114342019-10-29 09:30:44 +0100755 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400756 self.log.info("Deleting execution environment ee_id={}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100757
quilesj29114342019-10-29 09:30:44 +0100758 # check arguments
759 if ee_id is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400760 raise N2VCBadArgumentsException(
761 message="ee_id is mandatory", bad_args=["ee_id"]
762 )
quilesj29114342019-10-29 09:30:44 +0100763
beierlmf52cb7c2020-04-21 16:36:35 -0400764 model_name, application_name, _machine_id = self._get_ee_id_components(
765 ee_id=ee_id
766 )
quilesj29114342019-10-29 09:30:44 +0100767
768 # destroy the application
769 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200770 await self.libjuju.destroy_model(
771 model_name=model_name, total_timeout=total_timeout
beierlmf52cb7c2020-04-21 16:36:35 -0400772 )
quilesj29114342019-10-29 09:30:44 +0100773 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400774 raise N2VCException(
775 message=(
776 "Error deleting execution environment {} (application {}) : {}"
777 ).format(ee_id, application_name, e)
778 )
quilesj29114342019-10-29 09:30:44 +0100779
780 # destroy the machine
beierlmf52cb7c2020-04-21 16:36:35 -0400781 # try:
calvinosanch41395222020-02-21 09:25:21 +0100782 # await self._juju_destroy_machine(
783 # model_name=model_name,
784 # machine_id=machine_id,
785 # total_timeout=total_timeout
786 # )
787 # except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400788 # raise N2VCException(
789 # message='Error deleting execution environment {} (machine {}) : {}'
790 # .format(ee_id, machine_id, e))
quilesj29114342019-10-29 09:30:44 +0100791
beierlmf52cb7c2020-04-21 16:36:35 -0400792 self.log.info("Execution environment {} deleted".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100793
794 async def exec_primitive(
beierlmf52cb7c2020-04-21 16:36:35 -0400795 self,
796 ee_id: str,
797 primitive_name: str,
798 params_dict: dict,
799 db_dict: dict = None,
800 progress_timeout: float = None,
801 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100802 ) -> str:
803
beierlmf52cb7c2020-04-21 16:36:35 -0400804 self.log.info(
805 "Executing primitive: {} on ee: {}, params: {}".format(
806 primitive_name, ee_id, params_dict
807 )
808 )
quilesj29114342019-10-29 09:30:44 +0100809
quilesj29114342019-10-29 09:30:44 +0100810 # check arguments
811 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400812 raise N2VCBadArgumentsException(
813 message="ee_id is mandatory", bad_args=["ee_id"]
814 )
quilesj29114342019-10-29 09:30:44 +0100815 if primitive_name is None or len(primitive_name) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400816 raise N2VCBadArgumentsException(
817 message="action_name is mandatory", bad_args=["action_name"]
818 )
quilesj29114342019-10-29 09:30:44 +0100819 if params_dict is None:
820 params_dict = dict()
821
822 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400823 (
824 model_name,
825 application_name,
826 _machine_id,
827 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
quilesj29114342019-10-29 09:30:44 +0100828 except Exception:
829 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400830 message="ee_id={} is not a valid execution environment id".format(
831 ee_id
832 ),
833 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100834 )
835
beierlmf52cb7c2020-04-21 16:36:35 -0400836 if primitive_name == "config":
quilesj29114342019-10-29 09:30:44 +0100837 # Special case: config primitive
838 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200839 await self.libjuju.configure_application(
quilesj29114342019-10-29 09:30:44 +0100840 model_name=model_name,
841 application_name=application_name,
842 config=params_dict,
quilesj29114342019-10-29 09:30:44 +0100843 )
David Garcia4fee80e2020-05-13 12:18:38 +0200844 actions = await self.libjuju.get_actions(
845 application_name=application_name, model_name=model_name,
846 )
847 self.log.debug(
848 "Application {} has these actions: {}".format(
849 application_name, actions
850 )
851 )
852 if "verify-ssh-credentials" in actions:
853 # execute verify-credentials
854 num_retries = 20
855 retry_timeout = 15.0
856 for _ in range(num_retries):
857 try:
858 self.log.debug("Executing action verify-ssh-credentials...")
859 output, ok = await self.libjuju.execute_action(
860 model_name=model_name,
861 application_name=application_name,
862 action_name="verify-ssh-credentials",
863 db_dict=db_dict,
864 progress_timeout=progress_timeout,
865 total_timeout=total_timeout,
866 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200867
868 if ok == "failed":
869 self.log.debug(
870 "Error executing verify-ssh-credentials: {}. Retrying..."
871 )
872 await asyncio.sleep(retry_timeout)
873
874 continue
David Garcia4fee80e2020-05-13 12:18:38 +0200875 self.log.debug("Result: {}, output: {}".format(ok, output))
876 break
877 except asyncio.CancelledError:
878 raise
David Garcia4fee80e2020-05-13 12:18:38 +0200879 else:
880 self.log.error(
881 "Error executing verify-ssh-credentials after {} retries. ".format(
882 num_retries
883 )
884 )
885 else:
886 msg = "Action verify-ssh-credentials does not exist in application {}".format(
887 application_name
888 )
889 self.log.debug(msg=msg)
quilesj29114342019-10-29 09:30:44 +0100890 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400891 self.log.error("Error configuring juju application: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100892 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400893 message="Error configuring application into ee={} : {}".format(
894 ee_id, e
895 ),
896 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100897 )
beierlmf52cb7c2020-04-21 16:36:35 -0400898 return "CONFIG OK"
quilesj29114342019-10-29 09:30:44 +0100899 else:
900 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200901 output, status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100902 model_name=model_name,
903 application_name=application_name,
904 action_name=primitive_name,
905 db_dict=db_dict,
906 progress_timeout=progress_timeout,
907 total_timeout=total_timeout,
908 **params_dict
909 )
beierlmf52cb7c2020-04-21 16:36:35 -0400910 if status == "completed":
quilesj29114342019-10-29 09:30:44 +0100911 return output
912 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400913 raise Exception("status is not completed: {}".format(status))
quilesj29114342019-10-29 09:30:44 +0100914 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400915 self.log.error(
916 "Error executing primitive {}: {}".format(primitive_name, e)
917 )
quilesj29114342019-10-29 09:30:44 +0100918 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400919 message="Error executing primitive {} into ee={} : {}".format(
920 primitive_name, ee_id, e
921 ),
922 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100923 )
924
925 async def disconnect(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400926 self.log.info("closing juju N2VC...")
David Garcia4fee80e2020-05-13 12:18:38 +0200927 try:
928 await self.libjuju.disconnect()
929 except Exception as e:
930 raise N2VCConnectionException(
931 message="Error disconnecting controller: {}".format(e), url=self.url
932 )
quilesj29114342019-10-29 09:30:44 +0100933
934 """
David Garcia4fee80e2020-05-13 12:18:38 +0200935####################################################################################
936################################### P R I V A T E ##################################
937####################################################################################
quilesj29114342019-10-29 09:30:44 +0100938 """
939
beierlmf52cb7c2020-04-21 16:36:35 -0400940 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +0100941
942 # write ee_id to database: _admin.deployed.VCA.x
943 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400944 the_table = db_dict["collection"]
945 the_filter = db_dict["filter"]
946 the_path = db_dict["path"]
947 if not the_path[-1] == ".":
948 the_path = the_path + "."
949 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100950 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +0100951 self.db.set_one(
952 table=the_table,
953 q_filter=the_filter,
954 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400955 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +0100956 )
tierno8ff11992020-03-26 09:51:11 +0000957 except asyncio.CancelledError:
958 raise
quilesj29114342019-10-29 09:30:44 +0100959 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400960 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100961
962 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -0400963 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +0100964 """
965 Build an execution environment id form model, application and machine
966 :param model_name:
967 :param application_name:
968 :param machine_id:
969 :return:
970 """
971 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -0400972 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +0100973
974 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -0400975 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +0100976 """
977 Get model, application and machine components from an execution environment id
978 :param ee_id:
979 :return: model_name, application_name, machine_id
980 """
981
982 if ee_id is None:
983 return None, None, None
984
985 # split components of id
beierlmf52cb7c2020-04-21 16:36:35 -0400986 parts = ee_id.split(".")
quilesj29114342019-10-29 09:30:44 +0100987 model_name = parts[0]
988 application_name = parts[1]
989 machine_id = parts[2]
990 return model_name, application_name, machine_id
991
992 def _get_application_name(self, namespace: str) -> str:
993 """
994 Build application name from namespace
995 :param namespace:
996 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
997 """
998
Adam Israel18046072019-12-08 21:44:29 -0500999 # TODO: Enforce the Juju 50-character application limit
1000
quilesj29114342019-10-29 09:30:44 +01001001 # split namespace components
beierlmf52cb7c2020-04-21 16:36:35 -04001002 _, _, vnf_id, vdu_id, vdu_count = self._get_namespace_components(
1003 namespace=namespace
1004 )
quilesj29114342019-10-29 09:30:44 +01001005
1006 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001007 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001008 else:
Adam Israel18046072019-12-08 21:44:29 -05001009 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001010 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001011
1012 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001013 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001014 else:
Adam Israel18046072019-12-08 21:44:29 -05001015 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001016 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001017
1018 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001019 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001020 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001021 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001022
beierlmf52cb7c2020-04-21 16:36:35 -04001023 application_name = "app-{}{}{}".format(vnf_id, vdu_id, vdu_count)
quilesj29114342019-10-29 09:30:44 +01001024
1025 return N2VCJujuConnector._format_app_name(application_name)
1026
quilesj29114342019-10-29 09:30:44 +01001027 def _create_juju_public_key(self):
1028 """Recreate the Juju public key on lcm container, if needed
1029 Certain libjuju commands expect to be run from the same machine as Juju
1030 is bootstrapped to. This method will write the public key to disk in
1031 that location: ~/.local/share/juju/ssh/juju_id_rsa.pub
1032 """
1033
1034 # Make sure that we have a public key before writing to disk
1035 if self.public_key is None or len(self.public_key) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001036 if "OSMLCM_VCA_PUBKEY" in os.environ:
1037 self.public_key = os.getenv("OSMLCM_VCA_PUBKEY", "")
quilesj29114342019-10-29 09:30:44 +01001038 if len(self.public_key) == 0:
1039 return
1040 else:
1041 return
1042
beierlmf52cb7c2020-04-21 16:36:35 -04001043 pk_path = "{}/.local/share/juju/ssh".format(os.path.expanduser("~"))
quilesj29114342019-10-29 09:30:44 +01001044 file_path = "{}/juju_id_rsa.pub".format(pk_path)
beierlmf52cb7c2020-04-21 16:36:35 -04001045 self.log.debug(
1046 "writing juju public key to file:\n{}\npublic key: {}".format(
1047 file_path, self.public_key
1048 )
1049 )
quilesj29114342019-10-29 09:30:44 +01001050 if not os.path.exists(pk_path):
1051 # create path and write file
1052 os.makedirs(pk_path)
beierlmf52cb7c2020-04-21 16:36:35 -04001053 with open(file_path, "w") as f:
1054 self.log.debug("Creating juju public key file: {}".format(file_path))
quilesj29114342019-10-29 09:30:44 +01001055 f.write(self.public_key)
1056 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001057 self.log.debug("juju public key file already exists: {}".format(file_path))
quilesj29114342019-10-29 09:30:44 +01001058
1059 @staticmethod
1060 def _format_model_name(name: str) -> str:
1061 """Format the name of the model.
1062
1063 Model names may only contain lowercase letters, digits and hyphens
1064 """
1065
beierlmf52cb7c2020-04-21 16:36:35 -04001066 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001067
1068 @staticmethod
1069 def _format_app_name(name: str) -> str:
1070 """Format the name of the application (in order to assure valid application name).
1071
1072 Application names have restrictions (run juju deploy --help):
1073 - contains lowercase letters 'a'-'z'
1074 - contains numbers '0'-'9'
1075 - contains hyphens '-'
1076 - starts with a lowercase letter
1077 - not two or more consecutive hyphens
1078 - after a hyphen, not a group with all numbers
1079 """
1080
1081 def all_numbers(s: str) -> bool:
1082 for c in s:
1083 if not c.isdigit():
1084 return False
1085 return True
1086
beierlmf52cb7c2020-04-21 16:36:35 -04001087 new_name = name.replace("_", "-")
1088 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001089 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001090 while new_name.find("--") >= 0:
1091 new_name = new_name.replace("--", "-")
1092 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001093
1094 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001095 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001096 for i in range(len(groups)):
1097 group = groups[i]
1098 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001099 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001100 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001101 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001102 app_name += group
1103
1104 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001105 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001106
1107 return app_name