blob: 0522028f095d5670f9ecfbb3280a91ea98f2d3b7 [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
David Garcia85755d12020-09-21 19:51:23 +0200658 async def get_metrics(self, model_name: str, application_name: str) -> dict:
659 return await self.libjuju.get_metrics(model_name, application_name)
660
quilesj29114342019-10-29 09:30:44 +0100661 async def add_relation(
beierlmf52cb7c2020-04-21 16:36:35 -0400662 self, ee_id_1: str, ee_id_2: str, endpoint_1: str, endpoint_2: str
quilesj29114342019-10-29 09:30:44 +0100663 ):
664
beierlmf52cb7c2020-04-21 16:36:35 -0400665 self.log.debug(
666 "adding new relation between {} and {}, endpoints: {}, {}".format(
667 ee_id_1, ee_id_2, endpoint_1, endpoint_2
668 )
669 )
quilesj29114342019-10-29 09:30:44 +0100670
quilesjaae10b42020-01-09 08:49:10 +0000671 # check arguments
672 if not ee_id_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400673 message = "EE 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100674 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400675 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_1"])
quilesjaae10b42020-01-09 08:49:10 +0000676 if not ee_id_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400677 message = "EE 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100678 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400679 raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_2"])
quilesjaae10b42020-01-09 08:49:10 +0000680 if not endpoint_1:
beierlmf52cb7c2020-04-21 16:36:35 -0400681 message = "endpoint 1 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100682 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400683 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_1"])
quilesjaae10b42020-01-09 08:49:10 +0000684 if not endpoint_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400685 message = "endpoint 2 is mandatory"
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100686 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400687 raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"])
quilesjaae10b42020-01-09 08:49:10 +0000688
quilesjaae10b42020-01-09 08:49:10 +0000689 # get the model, the applications and the machines from the ee_id's
beierlmf52cb7c2020-04-21 16:36:35 -0400690 model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1)
691 model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2)
quilesj29114342019-10-29 09:30:44 +0100692
693 # model must be the same
694 if model_1 != model_2:
beierlmf52cb7c2020-04-21 16:36:35 -0400695 message = "EE models are not the same: {} vs {}".format(ee_id_1, ee_id_2)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100696 self.log.error(message)
beierlmf52cb7c2020-04-21 16:36:35 -0400697 raise N2VCBadArgumentsException(
698 message=message, bad_args=["ee_id_1", "ee_id_2"]
699 )
quilesj29114342019-10-29 09:30:44 +0100700
701 # add juju relations between two applications
702 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200703 await self.libjuju.add_relation(
quilesjaae10b42020-01-09 08:49:10 +0000704 model_name=model_1,
David Garcia8331f7c2020-08-25 16:10:07 +0200705 endpoint_1="{}:{}".format(app_1, endpoint_1),
706 endpoint_2="{}:{}".format(app_2, endpoint_2),
quilesjaae10b42020-01-09 08:49:10 +0000707 )
quilesj29114342019-10-29 09:30:44 +0100708 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400709 message = "Error adding relation between {} and {}: {}".format(
710 ee_id_1, ee_id_2, e
711 )
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100712 self.log.error(message)
quilesj29114342019-10-29 09:30:44 +0100713 raise N2VCException(message=message)
714
beierlmf52cb7c2020-04-21 16:36:35 -0400715 async def remove_relation(self):
quilesj29114342019-10-29 09:30:44 +0100716 # TODO
beierlmf52cb7c2020-04-21 16:36:35 -0400717 self.log.info("Method not implemented yet")
718 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100719
beierlmf52cb7c2020-04-21 16:36:35 -0400720 async def deregister_execution_environments(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400721 self.log.info("Method not implemented yet")
722 raise MethodNotImplemented()
quilesj29114342019-10-29 09:30:44 +0100723
724 async def delete_namespace(
beierlmf52cb7c2020-04-21 16:36:35 -0400725 self, namespace: str, db_dict: dict = None, total_timeout: float = None
quilesj29114342019-10-29 09:30:44 +0100726 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400727 self.log.info("Deleting namespace={}".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100728
quilesj29114342019-10-29 09:30:44 +0100729 # check arguments
730 if namespace is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400731 raise N2VCBadArgumentsException(
732 message="namespace is mandatory", bad_args=["namespace"]
733 )
quilesj29114342019-10-29 09:30:44 +0100734
beierlmf52cb7c2020-04-21 16:36:35 -0400735 _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
736 namespace=namespace
737 )
quilesj29114342019-10-29 09:30:44 +0100738 if ns_id is not None:
quilesj29114342019-10-29 09:30:44 +0100739 try:
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200740 models = await self.libjuju.list_models(contains=ns_id)
741 for model in models:
742 await self.libjuju.destroy_model(
743 model_name=model, total_timeout=total_timeout
744 )
quilesj29114342019-10-29 09:30:44 +0100745 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400746 raise N2VCException(
747 message="Error deleting namespace {} : {}".format(namespace, e)
748 )
quilesj29114342019-10-29 09:30:44 +0100749 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400750 raise N2VCBadArgumentsException(
751 message="only ns_id is permitted to delete yet", bad_args=["namespace"]
752 )
quilesj29114342019-10-29 09:30:44 +0100753
beierlmf52cb7c2020-04-21 16:36:35 -0400754 self.log.info("Namespace {} deleted".format(namespace))
quilesj29114342019-10-29 09:30:44 +0100755
756 async def delete_execution_environment(
beierlmf52cb7c2020-04-21 16:36:35 -0400757 self, ee_id: str, db_dict: dict = None, total_timeout: float = None
quilesj29114342019-10-29 09:30:44 +0100758 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400759 self.log.info("Deleting execution environment ee_id={}".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100760
quilesj29114342019-10-29 09:30:44 +0100761 # check arguments
762 if ee_id is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400763 raise N2VCBadArgumentsException(
764 message="ee_id is mandatory", bad_args=["ee_id"]
765 )
quilesj29114342019-10-29 09:30:44 +0100766
beierlmf52cb7c2020-04-21 16:36:35 -0400767 model_name, application_name, _machine_id = self._get_ee_id_components(
768 ee_id=ee_id
769 )
quilesj29114342019-10-29 09:30:44 +0100770
771 # destroy the application
772 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200773 await self.libjuju.destroy_model(
774 model_name=model_name, total_timeout=total_timeout
beierlmf52cb7c2020-04-21 16:36:35 -0400775 )
quilesj29114342019-10-29 09:30:44 +0100776 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400777 raise N2VCException(
778 message=(
779 "Error deleting execution environment {} (application {}) : {}"
780 ).format(ee_id, application_name, e)
781 )
quilesj29114342019-10-29 09:30:44 +0100782
783 # destroy the machine
beierlmf52cb7c2020-04-21 16:36:35 -0400784 # try:
calvinosanch41395222020-02-21 09:25:21 +0100785 # await self._juju_destroy_machine(
786 # model_name=model_name,
787 # machine_id=machine_id,
788 # total_timeout=total_timeout
789 # )
790 # except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400791 # raise N2VCException(
792 # message='Error deleting execution environment {} (machine {}) : {}'
793 # .format(ee_id, machine_id, e))
quilesj29114342019-10-29 09:30:44 +0100794
beierlmf52cb7c2020-04-21 16:36:35 -0400795 self.log.info("Execution environment {} deleted".format(ee_id))
quilesj29114342019-10-29 09:30:44 +0100796
797 async def exec_primitive(
beierlmf52cb7c2020-04-21 16:36:35 -0400798 self,
799 ee_id: str,
800 primitive_name: str,
801 params_dict: dict,
802 db_dict: dict = None,
803 progress_timeout: float = None,
804 total_timeout: float = None,
quilesj29114342019-10-29 09:30:44 +0100805 ) -> str:
806
beierlmf52cb7c2020-04-21 16:36:35 -0400807 self.log.info(
808 "Executing primitive: {} on ee: {}, params: {}".format(
809 primitive_name, ee_id, params_dict
810 )
811 )
quilesj29114342019-10-29 09:30:44 +0100812
quilesj29114342019-10-29 09:30:44 +0100813 # check arguments
814 if ee_id is None or len(ee_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400815 raise N2VCBadArgumentsException(
816 message="ee_id is mandatory", bad_args=["ee_id"]
817 )
quilesj29114342019-10-29 09:30:44 +0100818 if primitive_name is None or len(primitive_name) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400819 raise N2VCBadArgumentsException(
820 message="action_name is mandatory", bad_args=["action_name"]
821 )
quilesj29114342019-10-29 09:30:44 +0100822 if params_dict is None:
823 params_dict = dict()
824
825 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400826 (
827 model_name,
828 application_name,
829 _machine_id,
830 ) = N2VCJujuConnector._get_ee_id_components(ee_id=ee_id)
quilesj29114342019-10-29 09:30:44 +0100831 except Exception:
832 raise N2VCBadArgumentsException(
beierlmf52cb7c2020-04-21 16:36:35 -0400833 message="ee_id={} is not a valid execution environment id".format(
834 ee_id
835 ),
836 bad_args=["ee_id"],
quilesj29114342019-10-29 09:30:44 +0100837 )
838
beierlmf52cb7c2020-04-21 16:36:35 -0400839 if primitive_name == "config":
quilesj29114342019-10-29 09:30:44 +0100840 # Special case: config primitive
841 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200842 await self.libjuju.configure_application(
quilesj29114342019-10-29 09:30:44 +0100843 model_name=model_name,
844 application_name=application_name,
845 config=params_dict,
quilesj29114342019-10-29 09:30:44 +0100846 )
David Garcia4fee80e2020-05-13 12:18:38 +0200847 actions = await self.libjuju.get_actions(
848 application_name=application_name, model_name=model_name,
849 )
850 self.log.debug(
851 "Application {} has these actions: {}".format(
852 application_name, actions
853 )
854 )
855 if "verify-ssh-credentials" in actions:
856 # execute verify-credentials
857 num_retries = 20
858 retry_timeout = 15.0
859 for _ in range(num_retries):
860 try:
861 self.log.debug("Executing action verify-ssh-credentials...")
862 output, ok = await self.libjuju.execute_action(
863 model_name=model_name,
864 application_name=application_name,
865 action_name="verify-ssh-credentials",
866 db_dict=db_dict,
867 progress_timeout=progress_timeout,
868 total_timeout=total_timeout,
869 )
Dominik Fleischmannb9513342020-06-09 11:57:14 +0200870
871 if ok == "failed":
872 self.log.debug(
873 "Error executing verify-ssh-credentials: {}. Retrying..."
874 )
875 await asyncio.sleep(retry_timeout)
876
877 continue
David Garcia4fee80e2020-05-13 12:18:38 +0200878 self.log.debug("Result: {}, output: {}".format(ok, output))
879 break
880 except asyncio.CancelledError:
881 raise
David Garcia4fee80e2020-05-13 12:18:38 +0200882 else:
883 self.log.error(
884 "Error executing verify-ssh-credentials after {} retries. ".format(
885 num_retries
886 )
887 )
888 else:
889 msg = "Action verify-ssh-credentials does not exist in application {}".format(
890 application_name
891 )
892 self.log.debug(msg=msg)
quilesj29114342019-10-29 09:30:44 +0100893 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400894 self.log.error("Error configuring juju application: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100895 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400896 message="Error configuring application into ee={} : {}".format(
897 ee_id, e
898 ),
899 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100900 )
beierlmf52cb7c2020-04-21 16:36:35 -0400901 return "CONFIG OK"
quilesj29114342019-10-29 09:30:44 +0100902 else:
903 try:
David Garcia4fee80e2020-05-13 12:18:38 +0200904 output, status = await self.libjuju.execute_action(
quilesj29114342019-10-29 09:30:44 +0100905 model_name=model_name,
906 application_name=application_name,
907 action_name=primitive_name,
908 db_dict=db_dict,
909 progress_timeout=progress_timeout,
910 total_timeout=total_timeout,
911 **params_dict
912 )
beierlmf52cb7c2020-04-21 16:36:35 -0400913 if status == "completed":
quilesj29114342019-10-29 09:30:44 +0100914 return output
915 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400916 raise Exception("status is not completed: {}".format(status))
quilesj29114342019-10-29 09:30:44 +0100917 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400918 self.log.error(
919 "Error executing primitive {}: {}".format(primitive_name, e)
920 )
quilesj29114342019-10-29 09:30:44 +0100921 raise N2VCExecutionException(
beierlmf52cb7c2020-04-21 16:36:35 -0400922 message="Error executing primitive {} into ee={} : {}".format(
923 primitive_name, ee_id, e
924 ),
925 primitive_name=primitive_name,
quilesj29114342019-10-29 09:30:44 +0100926 )
927
928 async def disconnect(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400929 self.log.info("closing juju N2VC...")
David Garcia4fee80e2020-05-13 12:18:38 +0200930 try:
931 await self.libjuju.disconnect()
932 except Exception as e:
933 raise N2VCConnectionException(
934 message="Error disconnecting controller: {}".format(e), url=self.url
935 )
quilesj29114342019-10-29 09:30:44 +0100936
937 """
David Garcia4fee80e2020-05-13 12:18:38 +0200938####################################################################################
939################################### P R I V A T E ##################################
940####################################################################################
quilesj29114342019-10-29 09:30:44 +0100941 """
942
beierlmf52cb7c2020-04-21 16:36:35 -0400943 def _write_ee_id_db(self, db_dict: dict, ee_id: str):
quilesj29114342019-10-29 09:30:44 +0100944
945 # write ee_id to database: _admin.deployed.VCA.x
946 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400947 the_table = db_dict["collection"]
948 the_filter = db_dict["filter"]
949 the_path = db_dict["path"]
950 if not the_path[-1] == ".":
951 the_path = the_path + "."
952 update_dict = {the_path + "ee_id": ee_id}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100953 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
quilesj29114342019-10-29 09:30:44 +0100954 self.db.set_one(
955 table=the_table,
956 q_filter=the_filter,
957 update_dict=update_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400958 fail_on_empty=True,
quilesj29114342019-10-29 09:30:44 +0100959 )
tierno8ff11992020-03-26 09:51:11 +0000960 except asyncio.CancelledError:
961 raise
quilesj29114342019-10-29 09:30:44 +0100962 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400963 self.log.error("Error writing ee_id to database: {}".format(e))
quilesj29114342019-10-29 09:30:44 +0100964
965 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -0400966 def _build_ee_id(model_name: str, application_name: str, machine_id: str):
quilesj29114342019-10-29 09:30:44 +0100967 """
968 Build an execution environment id form model, application and machine
969 :param model_name:
970 :param application_name:
971 :param machine_id:
972 :return:
973 """
974 # id for the execution environment
beierlmf52cb7c2020-04-21 16:36:35 -0400975 return "{}.{}.{}".format(model_name, application_name, machine_id)
quilesj29114342019-10-29 09:30:44 +0100976
977 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -0400978 def _get_ee_id_components(ee_id: str) -> (str, str, str):
quilesj29114342019-10-29 09:30:44 +0100979 """
980 Get model, application and machine components from an execution environment id
981 :param ee_id:
982 :return: model_name, application_name, machine_id
983 """
984
985 if ee_id is None:
986 return None, None, None
987
988 # split components of id
beierlmf52cb7c2020-04-21 16:36:35 -0400989 parts = ee_id.split(".")
quilesj29114342019-10-29 09:30:44 +0100990 model_name = parts[0]
991 application_name = parts[1]
992 machine_id = parts[2]
993 return model_name, application_name, machine_id
994
995 def _get_application_name(self, namespace: str) -> str:
996 """
997 Build application name from namespace
998 :param namespace:
999 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1000 """
1001
Adam Israel18046072019-12-08 21:44:29 -05001002 # TODO: Enforce the Juju 50-character application limit
1003
quilesj29114342019-10-29 09:30:44 +01001004 # split namespace components
beierlmf52cb7c2020-04-21 16:36:35 -04001005 _, _, vnf_id, vdu_id, vdu_count = self._get_namespace_components(
1006 namespace=namespace
1007 )
quilesj29114342019-10-29 09:30:44 +01001008
1009 if vnf_id is None or len(vnf_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001010 vnf_id = ""
quilesj29114342019-10-29 09:30:44 +01001011 else:
Adam Israel18046072019-12-08 21:44:29 -05001012 # Shorten the vnf_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001013 vnf_id = "vnf-" + vnf_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001014
1015 if vdu_id is None or len(vdu_id) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001016 vdu_id = ""
quilesj29114342019-10-29 09:30:44 +01001017 else:
Adam Israel18046072019-12-08 21:44:29 -05001018 # Shorten the vdu_id to its last twelve characters
beierlmf52cb7c2020-04-21 16:36:35 -04001019 vdu_id = "-vdu-" + vdu_id[-12:]
quilesj29114342019-10-29 09:30:44 +01001020
1021 if vdu_count is None or len(vdu_count) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001022 vdu_count = ""
quilesj29114342019-10-29 09:30:44 +01001023 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001024 vdu_count = "-cnt-" + vdu_count
quilesj29114342019-10-29 09:30:44 +01001025
beierlmf52cb7c2020-04-21 16:36:35 -04001026 application_name = "app-{}{}{}".format(vnf_id, vdu_id, vdu_count)
quilesj29114342019-10-29 09:30:44 +01001027
1028 return N2VCJujuConnector._format_app_name(application_name)
1029
quilesj29114342019-10-29 09:30:44 +01001030 def _create_juju_public_key(self):
1031 """Recreate the Juju public key on lcm container, if needed
1032 Certain libjuju commands expect to be run from the same machine as Juju
1033 is bootstrapped to. This method will write the public key to disk in
1034 that location: ~/.local/share/juju/ssh/juju_id_rsa.pub
1035 """
1036
1037 # Make sure that we have a public key before writing to disk
1038 if self.public_key is None or len(self.public_key) == 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001039 if "OSMLCM_VCA_PUBKEY" in os.environ:
1040 self.public_key = os.getenv("OSMLCM_VCA_PUBKEY", "")
quilesj29114342019-10-29 09:30:44 +01001041 if len(self.public_key) == 0:
1042 return
1043 else:
1044 return
1045
beierlmf52cb7c2020-04-21 16:36:35 -04001046 pk_path = "{}/.local/share/juju/ssh".format(os.path.expanduser("~"))
quilesj29114342019-10-29 09:30:44 +01001047 file_path = "{}/juju_id_rsa.pub".format(pk_path)
beierlmf52cb7c2020-04-21 16:36:35 -04001048 self.log.debug(
1049 "writing juju public key to file:\n{}\npublic key: {}".format(
1050 file_path, self.public_key
1051 )
1052 )
quilesj29114342019-10-29 09:30:44 +01001053 if not os.path.exists(pk_path):
1054 # create path and write file
1055 os.makedirs(pk_path)
beierlmf52cb7c2020-04-21 16:36:35 -04001056 with open(file_path, "w") as f:
1057 self.log.debug("Creating juju public key file: {}".format(file_path))
quilesj29114342019-10-29 09:30:44 +01001058 f.write(self.public_key)
1059 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001060 self.log.debug("juju public key file already exists: {}".format(file_path))
quilesj29114342019-10-29 09:30:44 +01001061
1062 @staticmethod
1063 def _format_model_name(name: str) -> str:
1064 """Format the name of the model.
1065
1066 Model names may only contain lowercase letters, digits and hyphens
1067 """
1068
beierlmf52cb7c2020-04-21 16:36:35 -04001069 return name.replace("_", "-").replace(" ", "-").lower()
quilesj29114342019-10-29 09:30:44 +01001070
1071 @staticmethod
1072 def _format_app_name(name: str) -> str:
1073 """Format the name of the application (in order to assure valid application name).
1074
1075 Application names have restrictions (run juju deploy --help):
1076 - contains lowercase letters 'a'-'z'
1077 - contains numbers '0'-'9'
1078 - contains hyphens '-'
1079 - starts with a lowercase letter
1080 - not two or more consecutive hyphens
1081 - after a hyphen, not a group with all numbers
1082 """
1083
1084 def all_numbers(s: str) -> bool:
1085 for c in s:
1086 if not c.isdigit():
1087 return False
1088 return True
1089
beierlmf52cb7c2020-04-21 16:36:35 -04001090 new_name = name.replace("_", "-")
1091 new_name = new_name.replace(" ", "-")
quilesj29114342019-10-29 09:30:44 +01001092 new_name = new_name.lower()
beierlmf52cb7c2020-04-21 16:36:35 -04001093 while new_name.find("--") >= 0:
1094 new_name = new_name.replace("--", "-")
1095 groups = new_name.split("-")
quilesj29114342019-10-29 09:30:44 +01001096
1097 # find 'all numbers' groups and prefix them with a letter
beierlmf52cb7c2020-04-21 16:36:35 -04001098 app_name = ""
quilesj29114342019-10-29 09:30:44 +01001099 for i in range(len(groups)):
1100 group = groups[i]
1101 if all_numbers(group):
beierlmf52cb7c2020-04-21 16:36:35 -04001102 group = "z" + group
quilesj29114342019-10-29 09:30:44 +01001103 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001104 app_name += "-"
quilesj29114342019-10-29 09:30:44 +01001105 app_name += group
1106
1107 if app_name[0].isdigit():
beierlmf52cb7c2020-04-21 16:36:35 -04001108 app_name = "z" + app_name
quilesj29114342019-10-29 09:30:44 +01001109
1110 return app_name