4 # Copyright 2016 RIFT.IO Inc
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 implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
30 class OpenmanoCommandFailed(Exception):
34 class OpenmanoUnexpectedOutput(Exception):
38 class VNFExistsError(Exception):
42 class InstanceStatusError(Exception):
46 class OpenmanoHttpAPI(object):
47 def __init__(self
, log
, host
, port
, tenant
):
53 self
._session
= requests
.Session()
55 def get_instance(self
, instance_uuid
):
56 url
= "http://{host}:{port}/openmano/{tenant}/instances/{instance}".format(
60 instance
=instance_uuid
,
63 resp
= self
._session
.get(url
)
65 resp
.raise_for_status()
66 except requests
.exceptions
.HTTPError
as e
:
67 raise InstanceStatusError(e
)
71 def get_instance_vm_console_url(self
, instance_uuid
, vim_uuid
):
72 url
= "http://{host}:{port}/openmano/{tenant}/instances/{instance}/action".format(
76 instance
=instance_uuid
,
79 console_types
= ("novnc", "spice-html5", "xvpnvc", "rdp-html5")
80 for console_type
in console_types
:
81 payload_input
= {"console":console_type
, "vms":[vim_uuid
]}
82 payload_data
= json
.dumps(payload_input
)
83 resp
= self
._session
.post(url
, headers
={'content-type': 'application/json'},
86 resp
.raise_for_status()
87 except requests
.exceptions
.HTTPError
as e
:
88 raise InstanceStatusError(e
)
90 if vim_uuid
in result
and (result
[vim_uuid
]["vim_result"] == 1 or result
[vim_uuid
]["vim_result"] == 200):
91 return result
[vim_uuid
]["description"]
96 url
= "http://{host}:{port}/openmano/{tenant}/vnfs".format(
101 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
104 resp
.raise_for_status()
105 except requests
.exceptions
.HTTPError
as e
:
106 raise InstanceStatusError(e
)
110 def vnf(self
, vnf_id
):
114 for vnf
in vnfs
["vnfs"]:
115 # Rift vnf ID gets mapped to osm_id in OpenMano
116 if vnf_id
== vnf
["osm_id"]:
117 vnf_uuid
= vnf
["uuid"]
119 except Exception as e
:
125 url
= "http://{host}:{port}/openmano/{tenant}/vnfs/{uuid}".format(
131 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
134 resp
.raise_for_status()
135 except requests
.exceptions
.HTTPError
as e
:
136 raise InstanceStatusError(e
)
138 return resp
.json()['vnf']
141 url
= "http://{host}:{port}/openmano/{tenant}/scenarios".format(
146 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
149 resp
.raise_for_status()
150 except requests
.exceptions
.HTTPError
as e
:
151 raise InstanceStatusError(e
)
155 def scenario(self
, scenario_id
):
158 scenarios
= self
.scenarios()
159 for scenario
in scenarios
["scenarios"]:
160 # Rift NS ID gets mapped to osm_id in OpenMano
161 if scenario_id
== scenario
["osm_id"]:
162 scenario_uuid
= scenario
["uuid"]
164 except Exception as e
:
167 if not scenario_uuid
:
170 url
= "http://{host}:{port}/openmano/{tenant}/scenarios/{uuid}".format(
176 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
179 resp
.raise_for_status()
180 except requests
.exceptions
.HTTPError
as e
:
181 raise InstanceStatusError(e
)
183 return resp
.json()['scenario']
185 def post_vnfd_v3(self
, vnfd_body
):
186 # Check if the VNF is present at the RO
187 vnf_rift_id
= vnfd_body
["vnfd:vnfd-catalog"]["vnfd"][0]["id"]
188 vnf_check
= self
.vnf(vnf_rift_id
)
191 url
= "http://{host}:{port}/openmano/v3/{tenant}/vnfd".format(
196 payload_data
= json
.dumps(vnfd_body
)
197 resp
= self
._session
.post(url
, headers
={'content-type': 'application/json'},
200 resp
.raise_for_status()
201 except requests
.exceptions
.HTTPError
as e
:
202 raise InstanceStatusError(e
)
204 return resp
.json()['vnfd'][0]
209 def post_nsd_v3(self
, nsd_body
):
210 # Check if the NS (Scenario) is present at the RO
211 scenario_rift_id
= nsd_body
["nsd:nsd-catalog"]["nsd"][0]["id"]
212 scenario_check
= self
.scenario(scenario_rift_id
)
214 if not scenario_check
:
215 url
= "http://{host}:{port}/openmano/v3/{tenant}/nsd".format(
220 payload_data
= json
.dumps(nsd_body
)
221 resp
= self
._session
.post(url
, headers
={'content-type': 'application/json'},
224 resp
.raise_for_status()
225 except requests
.exceptions
.HTTPError
as e
:
226 raise InstanceStatusError(e
)
228 return resp
.json()['nsd'][0]
230 return scenario_check
233 class OpenmanoCliAPI(object):
234 """ This class implements the necessary funtionality to interact with """
238 def __init__(self
, log
, host
, port
, tenant
):
242 self
._tenant
= tenant
245 def openmano_cmd_path():
247 os
.environ
["RIFT_INSTALL"],
251 def _openmano_cmd(self
, arg_list
, expected_lines
=None):
252 cmd_args
= list(arg_list
)
253 cmd_args
.insert(0, self
.openmano_cmd_path())
256 "OPENMANO_HOST": self
._host
,
257 "OPENMANO_PORT": str(self
._port
),
258 "OPENMANO_TENANT": self
._tenant
,
262 "Running openmano command (%s) using env (%s)",
263 subprocess
.list2cmdline(cmd_args
),
267 proc
= subprocess
.Popen(
269 stdout
=subprocess
.PIPE
,
270 stderr
=subprocess
.PIPE
,
271 universal_newlines
=True,
275 stdout
, stderr
= proc
.communicate(timeout
=self
.CMD_TIMEOUT
)
276 except subprocess
.TimeoutExpired
:
277 self
._log
.error("Openmano command timed out")
279 stdout
, stderr
= proc
.communicate(timeout
=self
.CMD_TIMEOUT
)
281 if proc
.returncode
!= 0:
283 "Openmano command %s failed (rc=%s) with stdout: %s",
284 cmd_args
[1], proc
.returncode
, stdout
286 raise OpenmanoCommandFailed(stdout
)
288 self
._log
.debug("Openmano command completed with stdout: %s", stdout
)
290 output_lines
= stdout
.splitlines()
291 if expected_lines
is not None:
292 if len(output_lines
) != expected_lines
:
293 msg
= "Expected %s lines from openmano command. Got %s" % (expected_lines
, len(output_lines
))
295 raise OpenmanoUnexpectedOutput(msg
)
300 def vnf_create(self
, vnf_yaml_str
):
301 """ Create a Openmano VNF from a Openmano VNF YAML string """
303 self
._log
.debug("Creating VNF: %s", vnf_yaml_str
)
305 with tempfile
.NamedTemporaryFile() as vnf_file_hdl
:
306 vnf_file_hdl
.write(vnf_yaml_str
.encode())
310 output_lines
= self
._openmano
_cmd
(
311 ["vnf-create", vnf_file_hdl
.name
],
314 except OpenmanoCommandFailed
as e
:
315 if "already in use" in str(e
):
316 raise VNFExistsError("VNF was already added")
319 vnf_info_line
= output_lines
[0]
320 vnf_id
, vnf_name
= vnf_info_line
.split(" ", 1)
322 self
._log
.info("VNF %s Created: %s", vnf_name
, vnf_id
)
324 return vnf_id
, vnf_name
326 def vnf_delete(self
, vnf_uuid
):
328 ["vnf-delete", vnf_uuid
, "-f"],
331 self
._log
.info("VNF Deleted: %s", vnf_uuid
)
335 output_lines
= self
._openmano
_cmd
(
339 except OpenmanoCommandFailed
as e
:
340 self
._log
.warning("Vnf listing returned an error: %s", str(e
))
344 for line
in output_lines
:
346 uuid
, name
= line
.split(" ", 1)
347 name_uuid_map
[name
.strip()] = uuid
.strip()
349 self
._log
.debug("VNF list: {}".format(name_uuid_map
))
352 def ns_create(self
, ns_yaml_str
, name
=None):
353 self
._log
.info("Creating NS: %s", ns_yaml_str
)
355 with tempfile
.NamedTemporaryFile() as ns_file_hdl
:
356 ns_file_hdl
.write(ns_yaml_str
.encode())
359 cmd_args
= ["scenario-create", ns_file_hdl
.name
]
361 cmd_args
.extend(["--name", name
])
363 output_lines
= self
._openmano
_cmd
(
368 ns_info_line
= output_lines
[0]
369 ns_id
, ns_name
= ns_info_line
.split(" ", 1)
371 self
._log
.info("NS %s Created: %s", ns_name
, ns_id
)
373 return ns_id
, ns_name
376 self
._log
.debug("Getting NS list")
379 output_lines
= self
._openmano
_cmd
(
383 except OpenmanoCommandFailed
as e
:
384 self
._log
.warning("NS listing returned an error: %s", str(e
))
388 for line
in output_lines
:
390 uuid
, name
= line
.split(" ", 1)
391 name_uuid_map
[name
.strip()] = uuid
.strip()
393 self
._log
.debug("Scenario list: {}".format(name_uuid_map
))
396 def ns_delete(self
, ns_uuid
):
397 self
._log
.info("Deleting NS: %s", ns_uuid
)
400 ["scenario-delete", ns_uuid
, "-f"],
403 self
._log
.info("NS Deleted: %s", ns_uuid
)
405 def ns_instance_list(self
):
406 self
._log
.debug("Getting NS instance list")
409 output_lines
= self
._openmano
_cmd
(
410 ["instance-scenario-list"],
413 except OpenmanoCommandFailed
as e
:
414 self
._log
.warning("Instance scenario listing returned an error: %s", str(e
))
417 if "No scenario instances were found" in output_lines
[0]:
418 self
._log
.debug("No openmano instances were found")
422 for line
in output_lines
:
424 uuid
, name
= line
.split(" ", 1)
425 name_uuid_map
[name
.strip()] = uuid
.strip()
427 self
._log
.debug("Instance Scenario list: {}".format(name_uuid_map
))
430 def ns_instance_scenario_create(self
, instance_yaml_str
):
431 """ Create a Openmano NS instance from input YAML string """
433 self
._log
.debug("Instantiating instance: %s", instance_yaml_str
)
435 with tempfile
.NamedTemporaryFile() as ns_instance_file_hdl
:
436 ns_instance_file_hdl
.write(instance_yaml_str
.encode())
437 ns_instance_file_hdl
.flush()
440 output_lines
= self
._openmano
_cmd
(
441 ["instance-scenario-create", ns_instance_file_hdl
.name
],
444 except OpenmanoCommandFailed
as e
:
447 uuid
, _
= output_lines
[0].split(" ", 1)
449 self
._log
.info("NS Instance Created: %s", uuid
)
454 def ns_vim_network_create(self
, net_create_yaml_str
,datacenter_name
):
455 """ Create a Openmano VIM network from input YAML string """
457 self
._log
.debug("Creating VIM network instance: %s, DC %s", net_create_yaml_str
,datacenter_name
)
459 with tempfile
.NamedTemporaryFile() as net_create_file_hdl
:
460 net_create_file_hdl
.write(net_create_yaml_str
.encode())
461 net_create_file_hdl
.flush()
464 output_lines
= self
._openmano
_cmd
(
465 ["vim-net-create","--datacenter", datacenter_name
, net_create_file_hdl
.name
],
468 except OpenmanoCommandFailed
as e
:
471 uuid
, _
= output_lines
[0].split(" ", 1)
473 self
._log
.info("VIM Networks created in DC %s with ID: %s", datacenter_name
, uuid
)
477 def ns_vim_network_delete(self
, network_name
,datacenter_name
):
478 """ Delete a Openmano VIM network with given name """
480 self
._log
.debug("Deleting VIM network instance: %s, DC %s", network_name
,datacenter_name
)
482 output_lines
= self
._openmano
_cmd
(
483 ["vim-net-delete","--datacenter", datacenter_name
, network_name
],
486 except OpenmanoCommandFailed
as e
:
488 self
._log
.info("VIM Network deleted in DC %s with name: %s", datacenter_name
, network_name
)
491 def ns_instantiate(self
, scenario_name
, instance_name
, datacenter_name
=None):
493 "Instantiating NS %s using instance name %s",
498 cmd_args
= ["scenario-deploy", scenario_name
, instance_name
]
499 if datacenter_name
is not None:
500 cmd_args
.extend(["--datacenter", datacenter_name
])
502 output_lines
= self
._openmano
_cmd
(
507 uuid
, _
= output_lines
[0].split(" ", 1)
509 self
._log
.info("NS Instance Created: %s", uuid
)
513 def ns_terminate(self
, ns_instance_name
):
514 self
._log
.info("Terminating NS: %s", ns_instance_name
)
517 ["instance-scenario-delete", ns_instance_name
, "-f"],
520 self
._log
.info("NS Instance Deleted: %s", ns_instance_name
)
522 def datacenter_list(self
):
523 lines
= self
._openmano
_cmd
(["datacenter-list",])
525 # The results returned from openmano are formatted with whitespace and
526 # datacenter names may contain whitespace as well, so we use a regular
527 # expression to parse each line of the results return from openmano to
528 # extract the uuid and name of a datacenter.
530 uuid_pattern
= '(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)'.replace('x', hex)
531 name_pattern
= '(.+?)'
532 datacenter_regex
= re
.compile(r
'{uuid}\s+\b{name}\s*$'.format(
537 # Parse the results for the datacenter uuids and names
540 result
= datacenter_regex
.match(line
)
541 if result
is not None:
542 uuid
, name
= result
.groups()
543 datacenters
.append((uuid
, name
))
548 def valid_uuid(uuid_str
):
549 uuid_re
= re
.compile(
550 "^xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx$".replace('x', '[0-9a-fA-F]')
553 if not uuid_re
.match(uuid_str
):
554 raise argparse
.ArgumentTypeError("Got a valid uuid: %s" % uuid_str
)
559 def parse_args(argv
=sys
.argv
[1:]):
560 """ Parse the command line arguments
563 argv - The list of arguments to parse
566 Argparse Namespace instance
568 parser
= argparse
.ArgumentParser()
572 help="Openmano host/ip",
578 help="Openmano port",
585 help="Openmano tenant uuid to use",
588 subparsers
= parser
.add_subparsers(dest
='command', help='openmano commands')
590 vnf_create_parser
= subparsers
.add_parser(
592 help="Adds a openmano vnf into the catalog"
594 vnf_create_parser
.add_argument(
596 help="location of the JSON file describing the VNF",
597 type=argparse
.FileType('rb'),
600 vnf_delete_parser
= subparsers
.add_parser(
602 help="Deletes a openmano vnf into the catalog"
604 vnf_delete_parser
.add_argument(
606 help="The vnf to delete",
610 _
= subparsers
.add_parser(
612 help="List all the openmano VNFs in the catalog",
615 ns_create_parser
= subparsers
.add_parser(
617 help="Adds a openmano ns scenario into the catalog"
619 ns_create_parser
.add_argument(
621 help="location of the JSON file describing the NS",
622 type=argparse
.FileType('rb'),
625 ns_delete_parser
= subparsers
.add_parser(
627 help="Deletes a openmano ns into the catalog"
629 ns_delete_parser
.add_argument(
631 help="The ns to delete",
635 _
= subparsers
.add_parser(
637 help="List all the openmano scenarios in the catalog",
640 ns_instance_create_parser
= subparsers
.add_parser(
642 help="Deploys a openmano ns scenario into the catalog"
644 ns_instance_create_parser
.add_argument(
646 help="The ns scenario name to deploy",
648 ns_instance_create_parser
.add_argument(
650 help="The ns instance name to deploy",
654 ns_instance_delete_parser
= subparsers
.add_parser(
655 'instance-scenario-delete',
656 help="Deploys a openmano ns scenario into the catalog"
658 ns_instance_delete_parser
.add_argument(
660 help="The ns instance name to delete",
664 _
= subparsers
.add_parser(
665 'instance-scenario-list',
666 help="List all the openmano scenario instances in the catalog",
669 _
= subparsers
.add_parser(
671 help="List all the openmano datacenters",
674 args
= parser
.parse_args(argv
)
680 logging
.basicConfig(level
=logging
.DEBUG
)
681 logger
= logging
.getLogger("openmano_client.py")
683 if "RIFT_INSTALL" not in os
.environ
:
684 logger
.error("Must be in rift-shell to run.")
688 openmano_cli
= OpenmanoCliAPI(logger
, args
.host
, args
.port
, args
.tenant
)
690 if args
.command
== "vnf-create":
691 openmano_cli
.vnf_create(args
.file.read())
693 elif args
.command
== "vnf-delete":
694 openmano_cli
.vnf_delete(args
.uuid
)
696 elif args
.command
== "vnf-list":
697 for uuid
, name
in openmano_cli
.vnf_list().items():
698 print("{} {}".format(uuid
, name
))
700 elif args
.command
== "scenario-create":
701 openmano_cli
.ns_create(args
.file.read())
703 elif args
.command
== "scenario-delete":
704 openmano_cli
.ns_delete(args
.uuid
)
706 elif args
.command
== "scenario-list":
707 for uuid
, name
in openmano_cli
.ns_list().items():
708 print("{} {}".format(uuid
, name
))
710 elif args
.command
== "scenario-deploy":
711 openmano_cli
.ns_instantiate(args
.scenario_name
, args
.instance_name
)
713 elif args
.command
== "instance-scenario-delete":
714 openmano_cli
.ns_terminate(args
.instance_name
)
716 elif args
.command
== "instance-scenario-list":
717 for uuid
, name
in openmano_cli
.ns_instance_list().items():
718 print("{} {}".format(uuid
, name
))
720 elif args
.command
== "datacenter-list":
721 for uuid
, name
in openmano_cli
.datacenter_list():
722 print("{} {}".format(uuid
, name
))
725 logger
.error("Unknown command: %s", args
.command
)
728 if __name__
== "__main__":