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 get_metrics(self
, model_name
: str, application_name
: str) -> dict:
659 return await self
.libjuju
.get_metrics(model_name
, application_name
)
661 async def add_relation(
662 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
666 "adding new relation between {} and {}, endpoints: {}, {}".format(
667 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
673 message
= "EE 1 is mandatory"
674 self
.log
.error(message
)
675 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
677 message
= "EE 2 is mandatory"
678 self
.log
.error(message
)
679 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
681 message
= "endpoint 1 is mandatory"
682 self
.log
.error(message
)
683 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
685 message
= "endpoint 2 is mandatory"
686 self
.log
.error(message
)
687 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
689 # get the model, the applications and the machines from the ee_id's
690 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
)
693 # model must be the same
694 if model_1
!= model_2
:
695 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
696 self
.log
.error(message
)
697 raise N2VCBadArgumentsException(
698 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
701 # add juju relations between two applications
703 await self
.libjuju
.add_relation(
705 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
706 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
708 except Exception as e
:
709 message
= "Error adding relation between {} and {}: {}".format(
712 self
.log
.error(message
)
713 raise N2VCException(message
=message
)
715 async def remove_relation(self
):
717 self
.log
.info("Method not implemented yet")
718 raise MethodNotImplemented()
720 async def deregister_execution_environments(self
):
721 self
.log
.info("Method not implemented yet")
722 raise MethodNotImplemented()
724 async def delete_namespace(
725 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
727 self
.log
.info("Deleting namespace={}".format(namespace
))
730 if namespace
is None:
731 raise N2VCBadArgumentsException(
732 message
="namespace is mandatory", bad_args
=["namespace"]
735 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
738 if ns_id
is not None:
740 models
= await self
.libjuju
.list_models(contains
=ns_id
)
742 await self
.libjuju
.destroy_model(
743 model_name
=model
, total_timeout
=total_timeout
745 except Exception as e
:
747 message
="Error deleting namespace {} : {}".format(namespace
, e
)
750 raise N2VCBadArgumentsException(
751 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
754 self
.log
.info("Namespace {} deleted".format(namespace
))
756 async def delete_execution_environment(
757 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None
759 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
763 raise N2VCBadArgumentsException(
764 message
="ee_id is mandatory", bad_args
=["ee_id"]
767 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
771 # destroy the application
773 await self
.libjuju
.destroy_model(
774 model_name
=model_name
, total_timeout
=total_timeout
776 except Exception as e
:
779 "Error deleting execution environment {} (application {}) : {}"
780 ).format(ee_id
, application_name
, e
)
783 # destroy the machine
785 # await self._juju_destroy_machine(
786 # model_name=model_name,
787 # machine_id=machine_id,
788 # total_timeout=total_timeout
790 # except Exception as e:
791 # raise N2VCException(
792 # message='Error deleting execution environment {} (machine {}) : {}'
793 # .format(ee_id, machine_id, e))
795 self
.log
.info("Execution environment {} deleted".format(ee_id
))
797 async def exec_primitive(
802 db_dict
: dict = None,
803 progress_timeout
: float = None,
804 total_timeout
: float = None,
808 "Executing primitive: {} on ee: {}, params: {}".format(
809 primitive_name
, ee_id
, params_dict
814 if ee_id
is None or len(ee_id
) == 0:
815 raise N2VCBadArgumentsException(
816 message
="ee_id is mandatory", bad_args
=["ee_id"]
818 if primitive_name
is None or len(primitive_name
) == 0:
819 raise N2VCBadArgumentsException(
820 message
="action_name is mandatory", bad_args
=["action_name"]
822 if params_dict
is None:
830 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
832 raise N2VCBadArgumentsException(
833 message
="ee_id={} is not a valid execution environment id".format(
839 if primitive_name
== "config":
840 # Special case: config primitive
842 await self
.libjuju
.configure_application(
843 model_name
=model_name
,
844 application_name
=application_name
,
847 actions
= await self
.libjuju
.get_actions(
848 application_name
=application_name
, model_name
=model_name
,
851 "Application {} has these actions: {}".format(
852 application_name
, actions
855 if "verify-ssh-credentials" in actions
:
856 # execute verify-credentials
859 for _
in range(num_retries
):
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",
867 progress_timeout
=progress_timeout
,
868 total_timeout
=total_timeout
,
873 "Error executing verify-ssh-credentials: {}. Retrying..."
875 await asyncio
.sleep(retry_timeout
)
878 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
880 except asyncio
.CancelledError
:
884 "Error executing verify-ssh-credentials after {} retries. ".format(
889 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
892 self
.log
.debug(msg
=msg
)
893 except Exception as e
:
894 self
.log
.error("Error configuring juju application: {}".format(e
))
895 raise N2VCExecutionException(
896 message
="Error configuring application into ee={} : {}".format(
899 primitive_name
=primitive_name
,
904 output
, status
= await self
.libjuju
.execute_action(
905 model_name
=model_name
,
906 application_name
=application_name
,
907 action_name
=primitive_name
,
909 progress_timeout
=progress_timeout
,
910 total_timeout
=total_timeout
,
913 if status
== "completed":
916 raise Exception("status is not completed: {}".format(status
))
917 except Exception as e
:
919 "Error executing primitive {}: {}".format(primitive_name
, e
)
921 raise N2VCExecutionException(
922 message
="Error executing primitive {} into ee={} : {}".format(
923 primitive_name
, ee_id
, e
925 primitive_name
=primitive_name
,
928 async def disconnect(self
):
929 self
.log
.info("closing juju N2VC...")
931 await self
.libjuju
.disconnect()
932 except Exception as e
:
933 raise N2VCConnectionException(
934 message
="Error disconnecting controller: {}".format(e
), url
=self
.url
938 ####################################################################################
939 ################################### P R I V A T E ##################################
940 ####################################################################################
943 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
945 # write ee_id to database: _admin.deployed.VCA.x
947 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
}
953 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
957 update_dict
=update_dict
,
960 except asyncio
.CancelledError
:
962 except Exception as e
:
963 self
.log
.error("Error writing ee_id to database: {}".format(e
))
966 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
968 Build an execution environment id form model, application and machine
970 :param application_name:
974 # id for the execution environment
975 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
978 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
980 Get model, application and machine components from an execution environment id
982 :return: model_name, application_name, machine_id
986 return None, None, None
988 # split components of id
989 parts
= ee_id
.split(".")
990 model_name
= parts
[0]
991 application_name
= parts
[1]
992 machine_id
= parts
[2]
993 return model_name
, application_name
, machine_id
995 def _get_application_name(self
, namespace
: str) -> str:
997 Build application name from namespace
999 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1002 # TODO: Enforce the Juju 50-character application limit
1004 # split namespace components
1005 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
1009 if vnf_id
is None or len(vnf_id
) == 0:
1012 # Shorten the vnf_id to its last twelve characters
1013 vnf_id
= "vnf-" + vnf_id
[-12:]
1015 if vdu_id
is None or len(vdu_id
) == 0:
1018 # Shorten the vdu_id to its last twelve characters
1019 vdu_id
= "-vdu-" + vdu_id
[-12:]
1021 if vdu_count
is None or len(vdu_count
) == 0:
1024 vdu_count
= "-cnt-" + vdu_count
1026 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1028 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1030 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
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:
1039 if "OSMLCM_VCA_PUBKEY" in os
.environ
:
1040 self
.public_key
= os
.getenv("OSMLCM_VCA_PUBKEY", "")
1041 if len(self
.public_key
) == 0:
1046 pk_path
= "{}/.local/share/juju/ssh".format(os
.path
.expanduser("~"))
1047 file_path
= "{}/juju_id_rsa.pub".format(pk_path
)
1049 "writing juju public key to file:\n{}\npublic key: {}".format(
1050 file_path
, self
.public_key
1053 if not os
.path
.exists(pk_path
):
1054 # create path and write file
1055 os
.makedirs(pk_path
)
1056 with
open(file_path
, "w") as f
:
1057 self
.log
.debug("Creating juju public key file: {}".format(file_path
))
1058 f
.write(self
.public_key
)
1060 self
.log
.debug("juju public key file already exists: {}".format(file_path
))
1063 def _format_model_name(name
: str) -> str:
1064 """Format the name of the model.
1066 Model names may only contain lowercase letters, digits and hyphens
1069 return name
.replace("_", "-").replace(" ", "-").lower()
1072 def _format_app_name(name
: str) -> str:
1073 """Format the name of the application (in order to assure valid application name).
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
1084 def all_numbers(s
: str) -> bool:
1090 new_name
= name
.replace("_", "-")
1091 new_name
= new_name
.replace(" ", "-")
1092 new_name
= new_name
.lower()
1093 while new_name
.find("--") >= 0:
1094 new_name
= new_name
.replace("--", "-")
1095 groups
= new_name
.split("-")
1097 # find 'all numbers' groups and prefix them with a letter
1099 for i
in range(len(groups
)):
1101 if all_numbers(group
):
1107 if app_name
[0].isdigit():
1108 app_name
= "z" + app_name