2 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3 # This file is part of OSM
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
10 # http://www.apache.org/licenses/LICENSE-2.0
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
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # For those usages not covered by the Apache License, Version 2.0 please
20 # contact with: nfvlabs@tid.es
30 from n2vc
.exceptions
import (
31 N2VCBadArgumentsException
,
33 N2VCConnectionException
,
34 N2VCExecutionException
,
35 N2VCInvalidCertificate
,
38 JujuK8sProxycharmNotSupported
,
40 from n2vc
.n2vc_conn
import N2VCConnector
41 from n2vc
.n2vc_conn
import obj_to_dict
, obj_to_yaml
42 from n2vc
.libjuju
import Libjuju
45 class N2VCJujuConnector(N2VCConnector
):
48 ####################################################################################
49 ################################### P U B L I C ####################################
50 ####################################################################################
53 BUILT_IN_CLOUDS
= ["localhost", "microk8s"]
61 url
: str = "127.0.0.1:17070",
62 username
: str = "admin",
63 vca_config
: dict = None,
66 """Initialize juju N2VC connector
69 # parent class constructor
70 N2VCConnector
.__init
__(
78 vca_config
=vca_config
,
79 on_update_db
=on_update_db
,
82 # silence websocket traffic log
83 logging
.getLogger("websockets.protocol").setLevel(logging
.INFO
)
84 logging
.getLogger("juju.client.connection").setLevel(logging
.WARN
)
85 logging
.getLogger("model").setLevel(logging
.WARN
)
87 self
.log
.info("Initializing N2VC juju connector...")
90 ##############################################################
92 ##############################################################
97 raise N2VCBadArgumentsException("Argument url is mandatory", ["url"])
98 url_parts
= url
.split(":")
99 if len(url_parts
) != 2:
100 raise N2VCBadArgumentsException(
101 "Argument url: bad format (localhost:port) -> {}".format(url
), ["url"]
103 self
.hostname
= url_parts
[0]
105 self
.port
= int(url_parts
[1])
107 raise N2VCBadArgumentsException(
108 "url port must be a number -> {}".format(url
), ["url"]
113 raise N2VCBadArgumentsException(
114 "Argument username is mandatory", ["username"]
118 if vca_config
is None:
119 raise N2VCBadArgumentsException(
120 "Argument vca_config is mandatory", ["vca_config"]
123 if "secret" in vca_config
:
124 self
.secret
= vca_config
["secret"]
126 raise N2VCBadArgumentsException(
127 "Argument vca_config.secret is mandatory", ["vca_config.secret"]
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()
132 if "public_key" in vca_config
:
133 self
.public_key
= vca_config
["public_key"]
135 self
.public_key
= None
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.
146 cacert
= base64
.b64decode(b64string
).decode("utf-8")
148 cacert
= re
.sub(r
"\\n", r
"\n", cacert
,)
149 except binascii
.Error
as e
:
150 self
.log
.debug("Caught binascii.Error: {}".format(e
))
151 raise N2VCInvalidCertificate(message
="Invalid CA Certificate")
155 self
.ca_cert
= vca_config
.get("ca_cert")
157 self
.ca_cert
= base64_to_cacert(vca_config
["ca_cert"])
159 if "api_proxy" in vca_config
and vca_config
["api_proxy"] != "":
160 self
.api_proxy
= vca_config
["api_proxy"]
162 "api_proxy for native charms configured: {}".format(self
.api_proxy
)
166 "api_proxy is not configured"
168 self
.api_proxy
= None
170 if "enable_os_upgrade" in vca_config
:
171 self
.enable_os_upgrade
= vca_config
["enable_os_upgrade"]
173 self
.enable_os_upgrade
= True
175 if "apt_mirror" in vca_config
:
176 self
.apt_mirror
= vca_config
["apt_mirror"]
178 self
.apt_mirror
= None
180 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')
187 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
191 False # while connecting to juju (to avoid duplicate connections)
193 self
._authenticated
= (
194 False # it will be True when juju connection be stablished
196 self
._creating
_model
= False # True during model creation
197 self
.libjuju
= Libjuju(
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
,
211 # create juju pub key file in lcm container at
212 # ./local/share/juju/ssh/juju_id_rsa.pub
213 self
._create
_juju
_public
_key
()
215 self
.log
.info("N2VC juju connector initialized")
217 async def get_status(self
, namespace
: str, yaml_format
: bool = True):
219 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
221 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
224 # model name is ns_id
226 if model_name
is None:
227 msg
= "Namespace {} not valid".format(namespace
)
229 raise N2VCBadArgumentsException(msg
, ["namespace"])
232 models
= await self
.libjuju
.list_models(contains
=ns_id
)
235 status
[m
] = await self
.libjuju
.get_model_status(m
)
238 return obj_to_yaml(status
)
240 return obj_to_dict(status
)
242 async def create_execution_environment(
246 reuse_ee_id
: str = None,
247 progress_timeout
: float = None,
248 total_timeout
: float = None,
252 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
253 namespace
, reuse_ee_id
259 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
269 ) = self
._get
_namespace
_components
(namespace
=namespace
)
270 # model name is ns_id
273 application_name
= self
._get
_application
_name
(namespace
=namespace
)
276 "model name: {}, application name: {}, machine_id: {}".format(
277 model_name
, application_name
, machine_id
281 # create or reuse a new juju machine
283 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(
286 model_name
=model_name
,
287 machine_id
=machine_id
,
289 progress_timeout
=progress_timeout
,
290 total_timeout
=total_timeout
,
292 # 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
),
298 self
.log
.debug("ee_id: {}".format(ee_id
))
301 # write ee_id in database
302 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
304 except Exception as e
:
305 message
= "Error creating machine on juju: {}".format(e
)
306 self
.log
.error(message
)
307 raise N2VCException(message
=message
)
309 # new machine credentials
311 "hostname": machine
.dns_name
,
315 "Execution environment created. ee_id: {}, credentials: {}".format(
320 return ee_id
, credentials
322 async def register_execution_environment(
327 progress_timeout
: float = None,
328 total_timeout
: float = None,
332 "Registering execution environment. namespace={}, credentials={}".format(
333 namespace
, credentials
337 if credentials
is None:
338 raise N2VCBadArgumentsException(
339 message
="credentials are mandatory", bad_args
=["credentials"]
341 if credentials
.get("hostname"):
342 hostname
= credentials
["hostname"]
344 raise N2VCBadArgumentsException(
345 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
347 if credentials
.get("username"):
348 username
= credentials
["username"]
350 raise N2VCBadArgumentsException(
351 message
="username is mandatory", bad_args
=["credentials.username"]
353 if "private_key_path" in credentials
:
354 private_key_path
= credentials
["private_key_path"]
356 # if not passed as argument, use generated private key path
357 private_key_path
= self
.private_key_path
359 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
366 application_name
= self
._get
_application
_name
(namespace
=namespace
)
368 # register machine on juju
370 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(
373 model_name
=model_name
,
376 private_key_path
=private_key_path
,
378 progress_timeout
=progress_timeout
,
379 total_timeout
=total_timeout
,
381 except Exception as e
:
382 self
.log
.error("Error registering machine: {}".format(e
))
384 message
="Error registering machine on juju: {}".format(e
)
387 self
.log
.info("Machine registered: {}".format(machine_id
))
389 # id for the execution environment
390 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
391 model_name
=model_name
,
392 application_name
=application_name
,
393 machine_id
=str(machine_id
),
396 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
400 async def install_configuration_sw(
405 progress_timeout
: float = None,
406 total_timeout
: float = None,
413 "Installing configuration sw on ee_id: {}, "
414 "artifact path: {}, db_dict: {}"
415 ).format(ee_id
, artifact_path
, db_dict
)
419 if ee_id
is None or len(ee_id
) == 0:
420 raise N2VCBadArgumentsException(
421 message
="ee_id is mandatory", bad_args
=["ee_id"]
423 if artifact_path
is None or len(artifact_path
) == 0:
424 raise N2VCBadArgumentsException(
425 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
428 raise N2VCBadArgumentsException(
429 message
="db_dict is mandatory", bad_args
=["db_dict"]
437 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
439 "model: {}, application: {}, machine: {}".format(
440 model_name
, application_name
, machine_id
444 raise N2VCBadArgumentsException(
445 message
="ee_id={} is not a valid execution environment id".format(
451 # remove // in charm path
452 while artifact_path
.find("//") >= 0:
453 artifact_path
= artifact_path
.replace("//", "/")
456 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
457 msg
= "artifact path does not exist: {}".format(artifact_path
)
458 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
460 if artifact_path
.startswith("/"):
461 full_path
= self
.fs
.path
+ artifact_path
463 full_path
= self
.fs
.path
+ "/" + artifact_path
466 await self
.libjuju
.deploy_charm(
467 model_name
=model_name
,
468 application_name
=application_name
,
470 machine_id
=machine_id
,
472 progress_timeout
=progress_timeout
,
473 total_timeout
=total_timeout
,
477 except Exception as e
:
479 message
="Error desploying charm into ee={} : {}".format(ee_id
, e
)
482 self
.log
.info("Configuration sw installed")
484 async def install_k8s_proxy_charm(
490 progress_timeout
: float = None,
491 total_timeout
: float = None,
495 Install a k8s proxy charm
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
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
512 :returns ee_id: execution environment id.
514 self
.log
.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
515 .format(charm_name
, artifact_path
, db_dict
))
517 if not self
.k8s_cloud
:
518 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
520 if artifact_path
is None or len(artifact_path
) == 0:
521 raise N2VCBadArgumentsException(
522 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
525 raise N2VCBadArgumentsException(message
='db_dict is mandatory', bad_args
=['db_dict'])
527 # remove // in charm path
528 while artifact_path
.find('//') >= 0:
529 artifact_path
= artifact_path
.replace('//', '/')
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'])
536 if artifact_path
.startswith('/'):
537 full_path
= self
.fs
.path
+ artifact_path
539 full_path
= self
.fs
.path
+ '/' + artifact_path
541 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
542 model_name
= '{}-k8s'.format(ns_id
)
544 await self
.libjuju
.add_model(model_name
, self
.k8s_cloud
)
545 application_name
= self
._get
_application
_name
(namespace
)
548 await self
.libjuju
.deploy_charm(
549 model_name
=model_name
,
550 application_name
=application_name
,
554 progress_timeout
=progress_timeout
,
555 total_timeout
=total_timeout
,
558 except Exception as e
:
559 raise N2VCException(message
='Error deploying charm: {}'.format(e
))
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
,
568 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
572 async def get_ee_ssh_public__key(
576 progress_timeout
: float = None,
577 total_timeout
: float = None,
582 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
583 ).format(ee_id
, db_dict
)
587 if ee_id
is None or len(ee_id
) == 0:
588 raise N2VCBadArgumentsException(
589 message
="ee_id is mandatory", bad_args
=["ee_id"]
592 raise N2VCBadArgumentsException(
593 message
="db_dict is mandatory", bad_args
=["db_dict"]
601 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
603 "model: {}, application: {}, machine: {}".format(
604 model_name
, application_name
, machine_id
608 raise N2VCBadArgumentsException(
609 message
="ee_id={} is not a valid execution environment id".format(
615 # try to execute ssh layer primitives (if exist):
621 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
623 # execute action: generate-ssh-key
625 output
, _status
= await self
.libjuju
.execute_action(
626 model_name
=model_name
,
627 application_name
=application_name
,
628 action_name
="generate-ssh-key",
630 progress_timeout
=progress_timeout
,
631 total_timeout
=total_timeout
,
633 except Exception as e
:
635 "Skipping exception while executing action generate-ssh-key: {}".format(
640 # execute action: get-ssh-public-key
642 output
, _status
= await self
.libjuju
.execute_action(
643 model_name
=model_name
,
644 application_name
=application_name
,
645 action_name
="get-ssh-public-key",
647 progress_timeout
=progress_timeout
,
648 total_timeout
=total_timeout
,
650 except Exception as e
:
651 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
653 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
655 # return public key if exists
656 return output
["pubkey"] if "pubkey" in output
else output
658 async def add_relation(
659 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
663 "adding new relation between {} and {}, endpoints: {}, {}".format(
664 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
670 message
= "EE 1 is mandatory"
671 self
.log
.error(message
)
672 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
674 message
= "EE 2 is mandatory"
675 self
.log
.error(message
)
676 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
678 message
= "endpoint 1 is mandatory"
679 self
.log
.error(message
)
680 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
682 message
= "endpoint 2 is mandatory"
683 self
.log
.error(message
)
684 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
686 # get the model, the applications and the machines from the ee_id's
687 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
)
690 # model must be the same
691 if model_1
!= model_2
:
692 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
693 self
.log
.error(message
)
694 raise N2VCBadArgumentsException(
695 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
698 # add juju relations between two applications
700 await self
.libjuju
.add_relation(
702 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
703 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
705 except Exception as e
:
706 message
= "Error adding relation between {} and {}: {}".format(
709 self
.log
.error(message
)
710 raise N2VCException(message
=message
)
712 async def remove_relation(self
):
714 self
.log
.info("Method not implemented yet")
715 raise MethodNotImplemented()
717 async def deregister_execution_environments(self
):
718 self
.log
.info("Method not implemented yet")
719 raise MethodNotImplemented()
721 async def delete_namespace(
722 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
724 self
.log
.info("Deleting namespace={}".format(namespace
))
727 if namespace
is None:
728 raise N2VCBadArgumentsException(
729 message
="namespace is mandatory", bad_args
=["namespace"]
732 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
735 if ns_id
is not None:
737 models
= await self
.libjuju
.list_models(contains
=ns_id
)
739 await self
.libjuju
.destroy_model(
740 model_name
=model
, total_timeout
=total_timeout
742 except Exception as e
:
744 message
="Error deleting namespace {} : {}".format(namespace
, e
)
747 raise N2VCBadArgumentsException(
748 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
751 self
.log
.info("Namespace {} deleted".format(namespace
))
753 async def delete_execution_environment(
754 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None
756 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
760 raise N2VCBadArgumentsException(
761 message
="ee_id is mandatory", bad_args
=["ee_id"]
764 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
768 # destroy the application
770 await self
.libjuju
.destroy_model(
771 model_name
=model_name
, total_timeout
=total_timeout
773 except Exception as e
:
776 "Error deleting execution environment {} (application {}) : {}"
777 ).format(ee_id
, application_name
, e
)
780 # destroy the machine
782 # await self._juju_destroy_machine(
783 # model_name=model_name,
784 # machine_id=machine_id,
785 # total_timeout=total_timeout
787 # except Exception as e:
788 # raise N2VCException(
789 # message='Error deleting execution environment {} (machine {}) : {}'
790 # .format(ee_id, machine_id, e))
792 self
.log
.info("Execution environment {} deleted".format(ee_id
))
794 async def exec_primitive(
799 db_dict
: dict = None,
800 progress_timeout
: float = None,
801 total_timeout
: float = None,
805 "Executing primitive: {} on ee: {}, params: {}".format(
806 primitive_name
, ee_id
, params_dict
811 if ee_id
is None or len(ee_id
) == 0:
812 raise N2VCBadArgumentsException(
813 message
="ee_id is mandatory", bad_args
=["ee_id"]
815 if primitive_name
is None or len(primitive_name
) == 0:
816 raise N2VCBadArgumentsException(
817 message
="action_name is mandatory", bad_args
=["action_name"]
819 if params_dict
is None:
827 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
829 raise N2VCBadArgumentsException(
830 message
="ee_id={} is not a valid execution environment id".format(
836 if primitive_name
== "config":
837 # Special case: config primitive
839 await self
.libjuju
.configure_application(
840 model_name
=model_name
,
841 application_name
=application_name
,
844 actions
= await self
.libjuju
.get_actions(
845 application_name
=application_name
, model_name
=model_name
,
848 "Application {} has these actions: {}".format(
849 application_name
, actions
852 if "verify-ssh-credentials" in actions
:
853 # execute verify-credentials
856 for _
in range(num_retries
):
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",
864 progress_timeout
=progress_timeout
,
865 total_timeout
=total_timeout
,
870 "Error executing verify-ssh-credentials: {}. Retrying..."
872 await asyncio
.sleep(retry_timeout
)
875 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
877 except asyncio
.CancelledError
:
881 "Error executing verify-ssh-credentials after {} retries. ".format(
886 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
889 self
.log
.debug(msg
=msg
)
890 except Exception as e
:
891 self
.log
.error("Error configuring juju application: {}".format(e
))
892 raise N2VCExecutionException(
893 message
="Error configuring application into ee={} : {}".format(
896 primitive_name
=primitive_name
,
901 output
, status
= await self
.libjuju
.execute_action(
902 model_name
=model_name
,
903 application_name
=application_name
,
904 action_name
=primitive_name
,
906 progress_timeout
=progress_timeout
,
907 total_timeout
=total_timeout
,
910 if status
== "completed":
913 raise Exception("status is not completed: {}".format(status
))
914 except Exception as e
:
916 "Error executing primitive {}: {}".format(primitive_name
, e
)
918 raise N2VCExecutionException(
919 message
="Error executing primitive {} into ee={} : {}".format(
920 primitive_name
, ee_id
, e
922 primitive_name
=primitive_name
,
925 async def disconnect(self
):
926 self
.log
.info("closing juju N2VC...")
928 await self
.libjuju
.disconnect()
929 except Exception as e
:
930 raise N2VCConnectionException(
931 message
="Error disconnecting controller: {}".format(e
), url
=self
.url
935 ####################################################################################
936 ################################### P R I V A T E ##################################
937 ####################################################################################
940 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
942 # write ee_id to database: _admin.deployed.VCA.x
944 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
}
950 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
954 update_dict
=update_dict
,
957 except asyncio
.CancelledError
:
959 except Exception as e
:
960 self
.log
.error("Error writing ee_id to database: {}".format(e
))
963 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
965 Build an execution environment id form model, application and machine
967 :param application_name:
971 # id for the execution environment
972 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
975 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
977 Get model, application and machine components from an execution environment id
979 :return: model_name, application_name, machine_id
983 return None, None, None
985 # split components of id
986 parts
= ee_id
.split(".")
987 model_name
= parts
[0]
988 application_name
= parts
[1]
989 machine_id
= parts
[2]
990 return model_name
, application_name
, machine_id
992 def _get_application_name(self
, namespace
: str) -> str:
994 Build application name from namespace
996 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
999 # TODO: Enforce the Juju 50-character application limit
1001 # split namespace components
1002 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
1006 if vnf_id
is None or len(vnf_id
) == 0:
1009 # Shorten the vnf_id to its last twelve characters
1010 vnf_id
= "vnf-" + vnf_id
[-12:]
1012 if vdu_id
is None or len(vdu_id
) == 0:
1015 # Shorten the vdu_id to its last twelve characters
1016 vdu_id
= "-vdu-" + vdu_id
[-12:]
1018 if vdu_count
is None or len(vdu_count
) == 0:
1021 vdu_count
= "-cnt-" + vdu_count
1023 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1025 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1027 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
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:
1036 if "OSMLCM_VCA_PUBKEY" in os
.environ
:
1037 self
.public_key
= os
.getenv("OSMLCM_VCA_PUBKEY", "")
1038 if len(self
.public_key
) == 0:
1043 pk_path
= "{}/.local/share/juju/ssh".format(os
.path
.expanduser("~"))
1044 file_path
= "{}/juju_id_rsa.pub".format(pk_path
)
1046 "writing juju public key to file:\n{}\npublic key: {}".format(
1047 file_path
, self
.public_key
1050 if not os
.path
.exists(pk_path
):
1051 # create path and write file
1052 os
.makedirs(pk_path
)
1053 with
open(file_path
, "w") as f
:
1054 self
.log
.debug("Creating juju public key file: {}".format(file_path
))
1055 f
.write(self
.public_key
)
1057 self
.log
.debug("juju public key file already exists: {}".format(file_path
))
1060 def _format_model_name(name
: str) -> str:
1061 """Format the name of the model.
1063 Model names may only contain lowercase letters, digits and hyphens
1066 return name
.replace("_", "-").replace(" ", "-").lower()
1069 def _format_app_name(name
: str) -> str:
1070 """Format the name of the application (in order to assure valid application name).
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
1081 def all_numbers(s
: str) -> bool:
1087 new_name
= name
.replace("_", "-")
1088 new_name
= new_name
.replace(" ", "-")
1089 new_name
= new_name
.lower()
1090 while new_name
.find("--") >= 0:
1091 new_name
= new_name
.replace("--", "-")
1092 groups
= new_name
.split("-")
1094 # find 'all numbers' groups and prefix them with a letter
1096 for i
in range(len(groups
)):
1098 if all_numbers(group
):
1104 if app_name
[0].isdigit():
1105 app_name
= "z" + app_name