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
27 from n2vc
.exceptions
import (
28 N2VCBadArgumentsException
,
30 N2VCConnectionException
,
31 N2VCExecutionException
,
34 JujuK8sProxycharmNotSupported
,
36 from n2vc
.n2vc_conn
import N2VCConnector
37 from n2vc
.n2vc_conn
import obj_to_dict
, obj_to_yaml
38 from n2vc
.libjuju
import Libjuju
39 from n2vc
.utils
import base64_to_cacert
42 class N2VCJujuConnector(N2VCConnector
):
45 ####################################################################################
46 ################################### P U B L I C ####################################
47 ####################################################################################
50 BUILT_IN_CLOUDS
= ["localhost", "microk8s"]
58 url
: str = "127.0.0.1:17070",
59 username
: str = "admin",
60 vca_config
: dict = None,
63 """Initialize juju N2VC connector
66 # parent class constructor
67 N2VCConnector
.__init
__(
75 vca_config
=vca_config
,
76 on_update_db
=on_update_db
,
79 # silence websocket traffic log
80 logging
.getLogger("websockets.protocol").setLevel(logging
.INFO
)
81 logging
.getLogger("juju.client.connection").setLevel(logging
.WARN
)
82 logging
.getLogger("model").setLevel(logging
.WARN
)
84 self
.log
.info("Initializing N2VC juju connector...")
87 ##############################################################
89 ##############################################################
94 raise N2VCBadArgumentsException("Argument url is mandatory", ["url"])
95 url_parts
= url
.split(":")
96 if len(url_parts
) != 2:
97 raise N2VCBadArgumentsException(
98 "Argument url: bad format (localhost:port) -> {}".format(url
), ["url"]
100 self
.hostname
= url_parts
[0]
102 self
.port
= int(url_parts
[1])
104 raise N2VCBadArgumentsException(
105 "url port must be a number -> {}".format(url
), ["url"]
110 raise N2VCBadArgumentsException(
111 "Argument username is mandatory", ["username"]
115 if vca_config
is None:
116 raise N2VCBadArgumentsException(
117 "Argument vca_config is mandatory", ["vca_config"]
120 if "secret" in vca_config
:
121 self
.secret
= vca_config
["secret"]
123 raise N2VCBadArgumentsException(
124 "Argument vca_config.secret is mandatory", ["vca_config.secret"]
127 # pubkey of juju client in osm machine: ~/.local/share/juju/ssh/juju_id_rsa.pub
128 # if exists, it will be written in lcm container: _create_juju_public_key()
129 if "public_key" in vca_config
:
130 self
.public_key
= vca_config
["public_key"]
132 self
.public_key
= None
134 # TODO: Verify ca_cert is valid before using. VCA will crash
135 # if the ca_cert isn't formatted correctly.
137 self
.ca_cert
= vca_config
.get("ca_cert")
139 self
.ca_cert
= base64_to_cacert(vca_config
["ca_cert"])
141 if "api_proxy" in vca_config
and vca_config
["api_proxy"] != "":
142 self
.api_proxy
= vca_config
["api_proxy"]
144 "api_proxy for native charms configured: {}".format(self
.api_proxy
)
148 "api_proxy is not configured"
150 self
.api_proxy
= None
152 if "enable_os_upgrade" in vca_config
:
153 self
.enable_os_upgrade
= vca_config
["enable_os_upgrade"]
155 self
.enable_os_upgrade
= True
157 if "apt_mirror" in vca_config
:
158 self
.apt_mirror
= vca_config
["apt_mirror"]
160 self
.apt_mirror
= None
162 self
.cloud
= vca_config
.get('cloud')
163 self
.k8s_cloud
= None
164 if "k8s_cloud" in vca_config
:
165 self
.k8s_cloud
= vca_config
.get("k8s_cloud")
166 self
.log
.debug('Arguments have been checked')
169 self
.controller
= None # it will be filled when connect to juju
170 self
.juju_models
= {} # model objects for every model_name
171 self
.juju_observers
= {} # model observers for every model_name
173 False # while connecting to juju (to avoid duplicate connections)
175 self
._authenticated
= (
176 False # it will be True when juju connection be stablished
178 self
._creating
_model
= False # True during model creation
179 self
.libjuju
= Libjuju(
181 api_proxy
=self
.api_proxy
,
182 enable_os_upgrade
=self
.enable_os_upgrade
,
183 apt_mirror
=self
.apt_mirror
,
184 username
=self
.username
,
185 password
=self
.secret
,
193 # create juju pub key file in lcm container at
194 # ./local/share/juju/ssh/juju_id_rsa.pub
195 self
._create
_juju
_public
_key
()
197 self
.log
.info("N2VC juju connector initialized")
199 async def get_status(self
, namespace
: str, yaml_format
: bool = True):
201 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
203 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
206 # model name is ns_id
208 if model_name
is None:
209 msg
= "Namespace {} not valid".format(namespace
)
211 raise N2VCBadArgumentsException(msg
, ["namespace"])
214 models
= await self
.libjuju
.list_models(contains
=ns_id
)
217 status
[m
] = await self
.libjuju
.get_model_status(m
)
220 return obj_to_yaml(status
)
222 return obj_to_dict(status
)
224 async def create_execution_environment(
228 reuse_ee_id
: str = None,
229 progress_timeout
: float = None,
230 total_timeout
: float = None,
234 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
235 namespace
, reuse_ee_id
241 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
251 ) = self
._get
_namespace
_components
(namespace
=namespace
)
252 # model name is ns_id
255 application_name
= self
._get
_application
_name
(namespace
=namespace
)
258 "model name: {}, application name: {}, machine_id: {}".format(
259 model_name
, application_name
, machine_id
263 # create or reuse a new juju machine
265 if not await self
.libjuju
.model_exists(model_name
):
266 await self
.libjuju
.add_model(model_name
, cloud_name
=self
.cloud
)
267 machine
, new
= await self
.libjuju
.create_machine(
268 model_name
=model_name
,
269 machine_id
=machine_id
,
271 progress_timeout
=progress_timeout
,
272 total_timeout
=total_timeout
,
274 # id for the execution environment
275 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
276 model_name
=model_name
,
277 application_name
=application_name
,
278 machine_id
=str(machine
.entity_id
),
280 self
.log
.debug("ee_id: {}".format(ee_id
))
283 # write ee_id in database
284 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
286 except Exception as e
:
287 message
= "Error creating machine on juju: {}".format(e
)
288 self
.log
.error(message
)
289 raise N2VCException(message
=message
)
291 # new machine credentials
293 "hostname": machine
.dns_name
,
297 "Execution environment created. ee_id: {}, credentials: {}".format(
302 return ee_id
, credentials
304 async def register_execution_environment(
309 progress_timeout
: float = None,
310 total_timeout
: float = None,
314 "Registering execution environment. namespace={}, credentials={}".format(
315 namespace
, credentials
319 if credentials
is None:
320 raise N2VCBadArgumentsException(
321 message
="credentials are mandatory", bad_args
=["credentials"]
323 if credentials
.get("hostname"):
324 hostname
= credentials
["hostname"]
326 raise N2VCBadArgumentsException(
327 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
329 if credentials
.get("username"):
330 username
= credentials
["username"]
332 raise N2VCBadArgumentsException(
333 message
="username is mandatory", bad_args
=["credentials.username"]
335 if "private_key_path" in credentials
:
336 private_key_path
= credentials
["private_key_path"]
338 # if not passed as argument, use generated private key path
339 private_key_path
= self
.private_key_path
341 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
348 application_name
= self
._get
_application
_name
(namespace
=namespace
)
350 # register machine on juju
352 if not await self
.libjuju
.model_exists(model_name
):
353 await self
.libjuju
.add_model(model_name
, cloud_name
=self
.cloud
)
354 machine_id
= await self
.libjuju
.provision_machine(
355 model_name
=model_name
,
358 private_key_path
=private_key_path
,
360 progress_timeout
=progress_timeout
,
361 total_timeout
=total_timeout
,
363 except Exception as e
:
364 self
.log
.error("Error registering machine: {}".format(e
))
366 message
="Error registering machine on juju: {}".format(e
)
369 self
.log
.info("Machine registered: {}".format(machine_id
))
371 # id for the execution environment
372 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
373 model_name
=model_name
,
374 application_name
=application_name
,
375 machine_id
=str(machine_id
),
378 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
382 async def install_configuration_sw(
387 progress_timeout
: float = None,
388 total_timeout
: float = None,
395 "Installing configuration sw on ee_id: {}, "
396 "artifact path: {}, db_dict: {}"
397 ).format(ee_id
, artifact_path
, db_dict
)
401 if ee_id
is None or len(ee_id
) == 0:
402 raise N2VCBadArgumentsException(
403 message
="ee_id is mandatory", bad_args
=["ee_id"]
405 if artifact_path
is None or len(artifact_path
) == 0:
406 raise N2VCBadArgumentsException(
407 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
410 raise N2VCBadArgumentsException(
411 message
="db_dict is mandatory", bad_args
=["db_dict"]
419 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
421 "model: {}, application: {}, machine: {}".format(
422 model_name
, application_name
, machine_id
426 raise N2VCBadArgumentsException(
427 message
="ee_id={} is not a valid execution environment id".format(
433 # remove // in charm path
434 while artifact_path
.find("//") >= 0:
435 artifact_path
= artifact_path
.replace("//", "/")
438 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
439 msg
= "artifact path does not exist: {}".format(artifact_path
)
440 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
442 if artifact_path
.startswith("/"):
443 full_path
= self
.fs
.path
+ artifact_path
445 full_path
= self
.fs
.path
+ "/" + artifact_path
448 await self
.libjuju
.deploy_charm(
449 model_name
=model_name
,
450 application_name
=application_name
,
452 machine_id
=machine_id
,
454 progress_timeout
=progress_timeout
,
455 total_timeout
=total_timeout
,
459 except Exception as e
:
461 message
="Error desploying charm into ee={} : {}".format(ee_id
, e
)
464 self
.log
.info("Configuration sw installed")
466 async def install_k8s_proxy_charm(
472 progress_timeout
: float = None,
473 total_timeout
: float = None,
477 Install a k8s proxy charm
479 :param charm_name: Name of the charm being deployed
480 :param namespace: collection of all the uuids related to the charm.
481 :param str artifact_path: where to locate the artifacts (parent folder) using
483 the final artifact path will be a combination of this artifact_path and
484 additional string from the config_dict (e.g. charm name)
485 :param dict db_dict: where to write into database when the status changes.
486 It contains a dict with
487 {collection: <str>, filter: {}, path: <str>},
488 e.g. {collection: "nsrs", filter:
489 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
490 :param float progress_timeout:
491 :param float total_timeout:
492 :param config: Dictionary with additional configuration
494 :returns ee_id: execution environment id.
496 self
.log
.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
497 .format(charm_name
, artifact_path
, db_dict
))
499 if not self
.k8s_cloud
:
500 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
502 if artifact_path
is None or len(artifact_path
) == 0:
503 raise N2VCBadArgumentsException(
504 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
507 raise N2VCBadArgumentsException(message
='db_dict is mandatory', bad_args
=['db_dict'])
509 # remove // in charm path
510 while artifact_path
.find('//') >= 0:
511 artifact_path
= artifact_path
.replace('//', '/')
514 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
515 msg
= 'artifact path does not exist: {}'.format(artifact_path
)
516 raise N2VCBadArgumentsException(message
=msg
, bad_args
=['artifact_path'])
518 if artifact_path
.startswith('/'):
519 full_path
= self
.fs
.path
+ artifact_path
521 full_path
= self
.fs
.path
+ '/' + artifact_path
523 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
524 model_name
= '{}-k8s'.format(ns_id
)
526 await self
.libjuju
.add_model(model_name
, self
.k8s_cloud
)
527 application_name
= self
._get
_application
_name
(namespace
)
530 await self
.libjuju
.deploy_charm(
531 model_name
=model_name
,
532 application_name
=application_name
,
536 progress_timeout
=progress_timeout
,
537 total_timeout
=total_timeout
,
540 except Exception as e
:
541 raise N2VCException(message
='Error deploying charm: {}'.format(e
))
543 self
.log
.info('K8s proxy charm installed')
544 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
545 model_name
=model_name
,
546 application_name
=application_name
,
550 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
554 async def get_ee_ssh_public__key(
558 progress_timeout
: float = None,
559 total_timeout
: float = None,
564 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
565 ).format(ee_id
, db_dict
)
569 if ee_id
is None or len(ee_id
) == 0:
570 raise N2VCBadArgumentsException(
571 message
="ee_id is mandatory", bad_args
=["ee_id"]
574 raise N2VCBadArgumentsException(
575 message
="db_dict is mandatory", bad_args
=["db_dict"]
583 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
585 "model: {}, application: {}, machine: {}".format(
586 model_name
, application_name
, machine_id
590 raise N2VCBadArgumentsException(
591 message
="ee_id={} is not a valid execution environment id".format(
597 # try to execute ssh layer primitives (if exist):
603 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
605 # execute action: generate-ssh-key
607 output
, _status
= await self
.libjuju
.execute_action(
608 model_name
=model_name
,
609 application_name
=application_name
,
610 action_name
="generate-ssh-key",
612 progress_timeout
=progress_timeout
,
613 total_timeout
=total_timeout
,
615 except Exception as e
:
617 "Skipping exception while executing action generate-ssh-key: {}".format(
622 # execute action: get-ssh-public-key
624 output
, _status
= await self
.libjuju
.execute_action(
625 model_name
=model_name
,
626 application_name
=application_name
,
627 action_name
="get-ssh-public-key",
629 progress_timeout
=progress_timeout
,
630 total_timeout
=total_timeout
,
632 except Exception as e
:
633 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
635 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
637 # return public key if exists
638 return output
["pubkey"] if "pubkey" in output
else output
640 async def get_metrics(self
, model_name
: str, application_name
: str) -> dict:
641 return await self
.libjuju
.get_metrics(model_name
, application_name
)
643 async def add_relation(
644 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
648 "adding new relation between {} and {}, endpoints: {}, {}".format(
649 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
655 message
= "EE 1 is mandatory"
656 self
.log
.error(message
)
657 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
659 message
= "EE 2 is mandatory"
660 self
.log
.error(message
)
661 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
663 message
= "endpoint 1 is mandatory"
664 self
.log
.error(message
)
665 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
667 message
= "endpoint 2 is mandatory"
668 self
.log
.error(message
)
669 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
671 # get the model, the applications and the machines from the ee_id's
672 model_1
, app_1
, _machine_1
= self
._get
_ee
_id
_components
(ee_id_1
)
673 model_2
, app_2
, _machine_2
= self
._get
_ee
_id
_components
(ee_id_2
)
675 # model must be the same
676 if model_1
!= model_2
:
677 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
678 self
.log
.error(message
)
679 raise N2VCBadArgumentsException(
680 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
683 # add juju relations between two applications
685 await self
.libjuju
.add_relation(
687 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
688 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
690 except Exception as e
:
691 message
= "Error adding relation between {} and {}: {}".format(
694 self
.log
.error(message
)
695 raise N2VCException(message
=message
)
697 async def remove_relation(self
):
699 self
.log
.info("Method not implemented yet")
700 raise MethodNotImplemented()
702 async def deregister_execution_environments(self
):
703 self
.log
.info("Method not implemented yet")
704 raise MethodNotImplemented()
706 async def delete_namespace(
707 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
709 self
.log
.info("Deleting namespace={}".format(namespace
))
712 if namespace
is None:
713 raise N2VCBadArgumentsException(
714 message
="namespace is mandatory", bad_args
=["namespace"]
717 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
720 if ns_id
is not None:
722 models
= await self
.libjuju
.list_models(contains
=ns_id
)
724 await self
.libjuju
.destroy_model(
725 model_name
=model
, total_timeout
=total_timeout
727 except Exception as e
:
729 message
="Error deleting namespace {} : {}".format(namespace
, e
)
732 raise N2VCBadArgumentsException(
733 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
736 self
.log
.info("Namespace {} deleted".format(namespace
))
738 async def delete_execution_environment(
739 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None
741 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
745 raise N2VCBadArgumentsException(
746 message
="ee_id is mandatory", bad_args
=["ee_id"]
749 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
753 # destroy the application
755 await self
.libjuju
.destroy_model(
756 model_name
=model_name
, total_timeout
=total_timeout
758 except Exception as e
:
761 "Error deleting execution environment {} (application {}) : {}"
762 ).format(ee_id
, application_name
, e
)
765 # destroy the machine
767 # await self._juju_destroy_machine(
768 # model_name=model_name,
769 # machine_id=machine_id,
770 # total_timeout=total_timeout
772 # except Exception as e:
773 # raise N2VCException(
774 # message='Error deleting execution environment {} (machine {}) : {}'
775 # .format(ee_id, machine_id, e))
777 self
.log
.info("Execution environment {} deleted".format(ee_id
))
779 async def exec_primitive(
784 db_dict
: dict = None,
785 progress_timeout
: float = None,
786 total_timeout
: float = None,
790 "Executing primitive: {} on ee: {}, params: {}".format(
791 primitive_name
, ee_id
, params_dict
796 if ee_id
is None or len(ee_id
) == 0:
797 raise N2VCBadArgumentsException(
798 message
="ee_id is mandatory", bad_args
=["ee_id"]
800 if primitive_name
is None or len(primitive_name
) == 0:
801 raise N2VCBadArgumentsException(
802 message
="action_name is mandatory", bad_args
=["action_name"]
804 if params_dict
is None:
812 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
814 raise N2VCBadArgumentsException(
815 message
="ee_id={} is not a valid execution environment id".format(
821 if primitive_name
== "config":
822 # Special case: config primitive
824 await self
.libjuju
.configure_application(
825 model_name
=model_name
,
826 application_name
=application_name
,
829 actions
= await self
.libjuju
.get_actions(
830 application_name
=application_name
, model_name
=model_name
,
833 "Application {} has these actions: {}".format(
834 application_name
, actions
837 if "verify-ssh-credentials" in actions
:
838 # execute verify-credentials
841 for _
in range(num_retries
):
843 self
.log
.debug("Executing action verify-ssh-credentials...")
844 output
, ok
= await self
.libjuju
.execute_action(
845 model_name
=model_name
,
846 application_name
=application_name
,
847 action_name
="verify-ssh-credentials",
849 progress_timeout
=progress_timeout
,
850 total_timeout
=total_timeout
,
855 "Error executing verify-ssh-credentials: {}. Retrying..."
857 await asyncio
.sleep(retry_timeout
)
860 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
862 except asyncio
.CancelledError
:
866 "Error executing verify-ssh-credentials after {} retries. ".format(
871 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
874 self
.log
.debug(msg
=msg
)
875 except Exception as e
:
876 self
.log
.error("Error configuring juju application: {}".format(e
))
877 raise N2VCExecutionException(
878 message
="Error configuring application into ee={} : {}".format(
881 primitive_name
=primitive_name
,
886 output
, status
= await self
.libjuju
.execute_action(
887 model_name
=model_name
,
888 application_name
=application_name
,
889 action_name
=primitive_name
,
891 progress_timeout
=progress_timeout
,
892 total_timeout
=total_timeout
,
895 if status
== "completed":
898 raise Exception("status is not completed: {}".format(status
))
899 except Exception as e
:
901 "Error executing primitive {}: {}".format(primitive_name
, e
)
903 raise N2VCExecutionException(
904 message
="Error executing primitive {} into ee={} : {}".format(
905 primitive_name
, ee_id
, e
907 primitive_name
=primitive_name
,
910 async def disconnect(self
):
911 self
.log
.info("closing juju N2VC...")
913 await self
.libjuju
.disconnect()
914 except Exception as e
:
915 raise N2VCConnectionException(
916 message
="Error disconnecting controller: {}".format(e
), url
=self
.url
920 ####################################################################################
921 ################################### P R I V A T E ##################################
922 ####################################################################################
925 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
927 # write ee_id to database: _admin.deployed.VCA.x
929 the_table
= db_dict
["collection"]
930 the_filter
= db_dict
["filter"]
931 the_path
= db_dict
["path"]
932 if not the_path
[-1] == ".":
933 the_path
= the_path
+ "."
934 update_dict
= {the_path
+ "ee_id": ee_id
}
935 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
939 update_dict
=update_dict
,
942 except asyncio
.CancelledError
:
944 except Exception as e
:
945 self
.log
.error("Error writing ee_id to database: {}".format(e
))
948 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
950 Build an execution environment id form model, application and machine
952 :param application_name:
956 # id for the execution environment
957 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
960 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
962 Get model, application and machine components from an execution environment id
964 :return: model_name, application_name, machine_id
968 return None, None, None
970 # split components of id
971 parts
= ee_id
.split(".")
972 model_name
= parts
[0]
973 application_name
= parts
[1]
974 machine_id
= parts
[2]
975 return model_name
, application_name
, machine_id
977 def _get_application_name(self
, namespace
: str) -> str:
979 Build application name from namespace
981 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
984 # TODO: Enforce the Juju 50-character application limit
986 # split namespace components
987 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
991 if vnf_id
is None or len(vnf_id
) == 0:
994 # Shorten the vnf_id to its last twelve characters
995 vnf_id
= "vnf-" + vnf_id
[-12:]
997 if vdu_id
is None or len(vdu_id
) == 0:
1000 # Shorten the vdu_id to its last twelve characters
1001 vdu_id
= "-vdu-" + vdu_id
[-12:]
1003 if vdu_count
is None or len(vdu_count
) == 0:
1006 vdu_count
= "-cnt-" + vdu_count
1008 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1010 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1012 def _create_juju_public_key(self
):
1013 """Recreate the Juju public key on lcm container, if needed
1014 Certain libjuju commands expect to be run from the same machine as Juju
1015 is bootstrapped to. This method will write the public key to disk in
1016 that location: ~/.local/share/juju/ssh/juju_id_rsa.pub
1019 # Make sure that we have a public key before writing to disk
1020 if self
.public_key
is None or len(self
.public_key
) == 0:
1021 if "OSMLCM_VCA_PUBKEY" in os
.environ
:
1022 self
.public_key
= os
.getenv("OSMLCM_VCA_PUBKEY", "")
1023 if len(self
.public_key
) == 0:
1028 pk_path
= "{}/.local/share/juju/ssh".format(os
.path
.expanduser("~"))
1029 file_path
= "{}/juju_id_rsa.pub".format(pk_path
)
1031 "writing juju public key to file:\n{}\npublic key: {}".format(
1032 file_path
, self
.public_key
1035 if not os
.path
.exists(pk_path
):
1036 # create path and write file
1037 os
.makedirs(pk_path
)
1038 with
open(file_path
, "w") as f
:
1039 self
.log
.debug("Creating juju public key file: {}".format(file_path
))
1040 f
.write(self
.public_key
)
1042 self
.log
.debug("juju public key file already exists: {}".format(file_path
))
1045 def _format_model_name(name
: str) -> str:
1046 """Format the name of the model.
1048 Model names may only contain lowercase letters, digits and hyphens
1051 return name
.replace("_", "-").replace(" ", "-").lower()
1054 def _format_app_name(name
: str) -> str:
1055 """Format the name of the application (in order to assure valid application name).
1057 Application names have restrictions (run juju deploy --help):
1058 - contains lowercase letters 'a'-'z'
1059 - contains numbers '0'-'9'
1060 - contains hyphens '-'
1061 - starts with a lowercase letter
1062 - not two or more consecutive hyphens
1063 - after a hyphen, not a group with all numbers
1066 def all_numbers(s
: str) -> bool:
1072 new_name
= name
.replace("_", "-")
1073 new_name
= new_name
.replace(" ", "-")
1074 new_name
= new_name
.lower()
1075 while new_name
.find("--") >= 0:
1076 new_name
= new_name
.replace("--", "-")
1077 groups
= new_name
.split("-")
1079 # find 'all numbers' groups and prefix them with a letter
1081 for i
in range(len(groups
)):
1083 if all_numbers(group
):
1089 if app_name
[0].isdigit():
1090 app_name
= "z" + app_name