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
26 from http
import HTTPStatus
32 from n2vc
.exceptions
import N2VCBadArgumentsException
33 from osm_common
.dbmongo
import DbException
36 from n2vc
.loggable
import Loggable
37 from n2vc
.utils
import JujuStatusToOSM
, N2VCDeploymentStatus
40 class N2VCConnector(abc
.ABC
, Loggable
):
41 """Generic N2VC connector
47 ####################################################################################
48 ################################### P U B L I C ####################################
49 ####################################################################################
60 """Initialize N2VC abstract connector. It defines de API for VCA connectors
62 :param object db: Mongo object managing the MongoDB (repo common DbBase)
63 :param object fs: FileSystem object managing the package artifacts (repo common
65 :param object log: the logging object to log to
66 :param on_update_db: callback called when n2vc connector updates database.
69 filter: e.g. {_id: <nsd-id> }
70 path: e.g. "_admin.deployed.VCA.3."
71 updated_data: e.g. , "{ _admin.deployed.VCA.3.status: 'xxx', etc }"
75 Loggable
.__init
__(self
, log
=log
, log_to_console
=True, prefix
="\nN2VC")
79 raise N2VCBadArgumentsException("Argument db is mandatory", ["db"])
81 raise N2VCBadArgumentsException("Argument fs is mandatory", ["fs"])
83 # store arguments into self
86 self
.on_update_db
= on_update_db
88 # generate private/public key-pair
89 self
.private_key_path
= None
90 self
.public_key_path
= None
93 async def get_status(self
, namespace
: str, yaml_format
: bool = True):
94 """Get namespace status
96 :param namespace: we obtain ns from namespace
97 :param yaml_format: returns a yaml string
100 # TODO: review which public key
101 def get_public_key(self
) -> str:
102 """Get the VCA ssh-public-key
104 Returns the SSH public key from local mahine, to be injected into virtual
105 machines to be managed by the VCA.
106 First run, a ssh keypair will be created.
107 The public key is injected into a VM so that we can provision the
108 machine with Juju, after which Juju will communicate with the VM
109 directly via the juju agent.
112 # Find the path where we expect our key lives (~/.ssh)
113 homedir
= os
.environ
.get("HOME")
115 self
.log
.warning("No HOME environment variable, using /tmp")
117 sshdir
= "{}/.ssh".format(homedir
)
118 if not os
.path
.exists(sshdir
):
121 self
.private_key_path
= "{}/id_n2vc_rsa".format(sshdir
)
122 self
.public_key_path
= "{}.pub".format(self
.private_key_path
)
124 # If we don't have a key generated, then we have to generate it using ssh-keygen
125 if not os
.path
.exists(self
.private_key_path
):
126 cmd
= "ssh-keygen -t {} -b {} -N '' -f {}".format(
127 "rsa", "4096", self
.private_key_path
129 # run command with arguments
130 subprocess
.check_output(shlex
.split(cmd
))
132 # Read the public key. Only one public key (one line) in the file
133 with
open(self
.public_key_path
, "r") as file:
134 public_key
= file.readline()
139 async def create_execution_environment(
143 reuse_ee_id
: str = None,
144 progress_timeout
: float = None,
145 total_timeout
: float = None,
147 """Create an Execution Environment. Returns when it is created or raises an
150 :param str namespace: Contains a dot separate string.
151 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
152 :param dict db_dict: where to write to database when the status changes.
153 It contains a dictionary with {collection: str, filter: {}, path: str},
154 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
155 "_admin.deployed.VCA.3"}
156 :param str reuse_ee_id: ee id from an older execution. It allows us to reuse an
158 :param float progress_timeout:
159 :param float total_timeout:
160 :returns str, dict: id of the new execution environment and credentials for it
161 (credentials can contains hostname, username, etc depending on
166 async def register_execution_environment(
171 progress_timeout
: float = None,
172 total_timeout
: float = None,
175 Register an existing execution environment at the VCA
177 :param str namespace: same as create_execution_environment method
178 :param dict credentials: credentials to access the existing execution
180 (it can contains hostname, username, path to private key, etc depending on
182 :param dict db_dict: where to write to database when the status changes.
183 It contains a dictionary with {collection: str, filter: {}, path: str},
184 e.g. {collection: "nsrs", filter:
185 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
186 :param float progress_timeout:
187 :param float total_timeout:
188 :returns str: id of the execution environment
192 async def install_configuration_sw(
197 progress_timeout
: float = None,
198 total_timeout
: float = None,
201 Install the software inside the execution environment identified by ee_id
203 :param str ee_id: the id of the execution environment returned by
204 create_execution_environment or register_execution_environment
205 :param str artifact_path: where to locate the artifacts (parent folder) using
207 the final artifact path will be a combination of this artifact_path and
208 additional string from the config_dict (e.g. charm name)
209 :param dict db_dict: where to write into database when the status changes.
210 It contains a dict with
211 {collection: <str>, filter: {}, path: <str>},
212 e.g. {collection: "nsrs", filter:
213 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
214 :param float progress_timeout:
215 :param float total_timeout:
219 async def install_k8s_proxy_charm(
225 progress_timeout
: float = None,
226 total_timeout
: float = None,
230 Install a k8s proxy charm
232 :param charm_name: Name of the charm being deployed
233 :param namespace: collection of all the uuids related to the charm.
234 :param str artifact_path: where to locate the artifacts (parent folder) using
236 the final artifact path will be a combination of this artifact_path and
237 additional string from the config_dict (e.g. charm name)
238 :param dict db_dict: where to write into database when the status changes.
239 It contains a dict with
240 {collection: <str>, filter: {}, path: <str>},
241 e.g. {collection: "nsrs", filter:
242 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
243 :param float progress_timeout:
244 :param float total_timeout:
245 :param config: Dictionary with additional configuration
247 :returns ee_id: execution environment id.
251 async def get_ee_ssh_public__key(
255 progress_timeout
: float = None,
256 total_timeout
: float = None,
259 Generate a priv/pub key pair in the execution environment and return the public
262 :param str ee_id: the id of the execution environment returned by
263 create_execution_environment or register_execution_environment
264 :param dict db_dict: where to write into database when the status changes.
265 It contains a dict with
266 {collection: <str>, filter: {}, path: <str>},
267 e.g. {collection: "nsrs", filter:
268 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
269 :param float progress_timeout:
270 :param float total_timeout:
271 :returns: public key of the execution environment
272 For the case of juju proxy charm ssh-layered, it is the one
273 returned by 'get-ssh-public-key' primitive.
274 It raises a N2VC exception if fails
278 async def add_relation(
279 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
282 Add a relation between two Execution Environments (using their associated
285 :param str ee_id_1: The id of the first execution environment
286 :param str ee_id_2: The id of the second execution environment
287 :param str endpoint_1: The endpoint in the first execution environment
288 :param str endpoint_2: The endpoint in the second execution environment
293 async def remove_relation(self
):
298 async def deregister_execution_environments(self
):
302 async def delete_namespace(
303 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
306 Remove a network scenario and its execution environments
307 :param namespace: [<nsi-id>].<ns-id>
308 :param dict db_dict: where to write into database when the status changes.
309 It contains a dict with
310 {collection: <str>, filter: {}, path: <str>},
311 e.g. {collection: "nsrs", filter:
312 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
313 :param float total_timeout:
317 async def delete_execution_environment(
318 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None
321 Delete an execution environment
322 :param str ee_id: id of the execution environment to delete
323 :param dict db_dict: where to write into database when the status changes.
324 It contains a dict with
325 {collection: <str>, filter: {}, path: <str>},
326 e.g. {collection: "nsrs", filter:
327 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
328 :param float total_timeout:
332 async def upgrade_charm(
336 charm_id
: str = None,
337 charm_type
: str = None,
338 timeout
: float = None,
340 """This method upgrade charms in VNFs
343 ee_id: Execution environment id
344 path: Local path to the charm
346 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
347 timeout: (Float) Timeout for the ns update operation
350 The output of the update operation if status equals to "completed"
354 async def exec_primitive(
359 db_dict
: dict = None,
360 progress_timeout
: float = None,
361 total_timeout
: float = None,
364 Execute a primitive in the execution environment
366 :param str ee_id: the one returned by create_execution_environment or
367 register_execution_environment
368 :param str primitive_name: must be one defined in the software. There is one
369 called 'config', where, for the proxy case, the 'credentials' of VM are
371 :param dict params_dict: parameters of the action
372 :param dict db_dict: where to write into database when the status changes.
373 It contains a dict with
374 {collection: <str>, filter: {}, path: <str>},
375 e.g. {collection: "nsrs", filter:
376 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
377 :param float progress_timeout:
378 :param float total_timeout:
379 :returns str: primitive result, if ok. It raises exceptions in case of fail
382 async def disconnect(self
):
388 ####################################################################################
389 ################################### P R I V A T E ##################################
390 ####################################################################################
393 def _get_namespace_components(self
, namespace
: str) -> (str, str, str, str, str):
395 Split namespace components
397 :param namespace: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
398 :return: nsi_id, ns_id, vnf_id, vdu_id, vdu_count
402 if namespace
is None or len(namespace
) == 0:
403 raise N2VCBadArgumentsException(
404 "Argument namespace is mandatory", ["namespace"]
407 # split namespace components
408 parts
= namespace
.split(".")
414 if len(parts
) > 0 and len(parts
[0]) > 0:
416 if len(parts
) > 1 and len(parts
[1]) > 0:
418 if len(parts
) > 2 and len(parts
[2]) > 0:
420 if len(parts
) > 3 and len(parts
[3]) > 0:
422 vdu_parts
= parts
[3].split("-")
423 if len(vdu_parts
) > 1:
424 vdu_id
= vdu_parts
[0]
425 vdu_count
= vdu_parts
[1]
427 return nsi_id
, ns_id
, vnf_id
, vdu_id
, vdu_count
429 async def write_app_status_to_db(
432 status
: N2VCDeploymentStatus
,
433 detailed_status
: str,
439 Write application status to database
441 :param: db_dict: DB dictionary
442 :param: status: Status of the application
443 :param: detailed_status: Detailed status
444 :param: vca_status: VCA status
445 :param: entity_type: Entity type ("application", "machine, and "action")
446 :param: vca_id: Id of the VCA. If None, the default VCA will be used.
449 self
.log
.debug("No db_dict => No database write")
452 # self.log.debug('status={} / detailed-status={} / VCA-status={}/entity_type={}'
453 # .format(str(status.value), detailed_status, vca_status, entity_type))
456 the_table
= db_dict
["collection"]
457 the_filter
= db_dict
["filter"]
458 the_path
= db_dict
["path"]
459 if not the_path
[-1] == ".":
460 the_path
= the_path
+ "."
462 the_path
+ "status": str(status
.value
),
463 the_path
+ "detailed-status": detailed_status
,
464 the_path
+ "VCA-status": vca_status
,
465 the_path
+ "entity-type": entity_type
,
466 the_path
+ "status-time": str(time
.time()),
472 update_dict
=update_dict
,
477 if self
.on_update_db
:
478 if asyncio
.iscoroutinefunction(self
.on_update_db
):
479 await self
.on_update_db(
480 the_table
, the_filter
, the_path
, update_dict
, vca_id
=vca_id
484 the_table
, the_filter
, the_path
, update_dict
, vca_id
=vca_id
487 except DbException
as e
:
488 if e
.http_code
== HTTPStatus
.NOT_FOUND
:
490 "NOT_FOUND error: Exception writing status to database: {}".format(
495 self
.log
.info("Exception writing status to database: {}".format(e
))
497 def osm_status(self
, entity_type
: str, status
: str) -> N2VCDeploymentStatus
:
498 if status
not in JujuStatusToOSM
[entity_type
]:
499 self
.log
.warning("Status {} not found in JujuStatusToOSM.".format(status
))
500 return N2VCDeploymentStatus
.UNKNOWN
501 return JujuStatusToOSM
[entity_type
][status
]
504 def obj_to_yaml(obj
: object) -> str:
506 dump_text
= yaml
.dump(obj
, default_flow_style
=False, indent
=2)
508 lines
= dump_text
.splitlines()
509 # remove !!python/object tags
512 index
= line
.find("!!python/object")
515 yaml_text
+= line
+ "\n"
519 def obj_to_dict(obj
: object) -> dict:
520 # convert obj to yaml
521 yaml_text
= obj_to_yaml(obj
)
523 return yaml
.load(yaml_text
, Loader
=yaml
.SafeLoader
)