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()
56 url
= "http://{host}:{port}/openmano/{tenant}/instances".format(
62 resp
= self
._session
.get(url
)
64 resp
.raise_for_status()
65 except requests
.exceptions
.HTTPError
as e
:
66 raise InstanceStatusError(e
)
68 return resp
.json()["instances"]
70 def get_instance(self
, instance_uuid
):
71 url
= "http://{host}:{port}/openmano/{tenant}/instances/{instance}".format(
75 instance
=instance_uuid
,
78 resp
= self
._session
.get(url
)
80 resp
.raise_for_status()
81 except requests
.exceptions
.HTTPError
as e
:
82 raise InstanceStatusError(e
)
86 def get_instance_vm_console_url(self
, instance_uuid
, vim_uuid
):
87 url
= "http://{host}:{port}/openmano/{tenant}/instances/{instance}/action".format(
91 instance
=instance_uuid
,
94 console_types
= ("novnc", "spice-html5", "xvpnvc", "rdp-html5")
95 for console_type
in console_types
:
96 payload_input
= {"console":console_type
, "vms":[vim_uuid
]}
97 payload_data
= json
.dumps(payload_input
)
98 resp
= self
._session
.post(url
, headers
={'content-type': 'application/json'},
101 resp
.raise_for_status()
102 except requests
.exceptions
.HTTPError
as e
:
103 raise InstanceStatusError(e
)
105 if vim_uuid
in result
and (result
[vim_uuid
]["vim_result"] == 1 or result
[vim_uuid
]["vim_result"] == 200):
106 return result
[vim_uuid
]["description"]
111 url
= "http://{host}:{port}/openmano/{tenant}/vnfs".format(
116 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
119 resp
.raise_for_status()
120 except requests
.exceptions
.HTTPError
as e
:
121 raise InstanceStatusError(e
)
125 def vnf(self
, vnf_id
):
129 for vnf
in vnfs
["vnfs"]:
130 # Rift vnf ID gets mapped to osm_id in OpenMano
131 if vnf_id
== vnf
["osm_id"]:
132 vnf_uuid
= vnf
["uuid"]
134 except Exception as e
:
140 url
= "http://{host}:{port}/openmano/{tenant}/vnfs/{uuid}".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
)
153 return resp
.json()['vnf']
156 url
= "http://{host}:{port}/openmano/{tenant}/scenarios".format(
161 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
164 resp
.raise_for_status()
165 except requests
.exceptions
.HTTPError
as e
:
166 raise InstanceStatusError(e
)
170 def scenario(self
, scenario_id
):
173 scenarios
= self
.scenarios()
174 for scenario
in scenarios
["scenarios"]:
175 # Rift NS ID gets mapped to osm_id in OpenMano
176 if scenario_id
== scenario
["osm_id"]:
177 scenario_uuid
= scenario
["uuid"]
179 except Exception as e
:
182 if not scenario_uuid
:
185 url
= "http://{host}:{port}/openmano/{tenant}/scenarios/{uuid}".format(
191 resp
= self
._session
.get(url
, headers
={'content-type': 'application/json'})
194 resp
.raise_for_status()
195 except requests
.exceptions
.HTTPError
as e
:
196 raise InstanceStatusError(e
)
198 return resp
.json()['scenario']
200 def post_vnfd_v3(self
, vnfd_body
):
201 # Check if the VNF is present at the RO
202 vnf_rift_id
= vnfd_body
["vnfd:vnfd-catalog"]["vnfd"][0]["id"]
203 vnf_check
= self
.vnf(vnf_rift_id
)
206 url
= "http://{host}:{port}/openmano/v3/{tenant}/vnfd".format(
211 payload_data
= json
.dumps(vnfd_body
)
212 resp
= self
._session
.post(url
, headers
={'content-type': 'application/json'},
215 resp
.raise_for_status()
216 except requests
.exceptions
.HTTPError
as e
:
217 raise InstanceStatusError(e
)
219 return resp
.json()['vnfd'][0]
224 def post_nsd_v3(self
, nsd_body
):
225 # Check if the NS (Scenario) is present at the RO
226 scenario_rift_id
= nsd_body
["nsd:nsd-catalog"]["nsd"][0]["id"]
227 scenario_check
= self
.scenario(scenario_rift_id
)
229 if not scenario_check
:
230 url
= "http://{host}:{port}/openmano/v3/{tenant}/nsd".format(
235 payload_data
= json
.dumps(nsd_body
)
236 resp
= self
._session
.post(url
, headers
={'content-type': 'application/json'},
239 resp
.raise_for_status()
240 except requests
.exceptions
.HTTPError
as e
:
241 raise InstanceStatusError(e
)
243 return resp
.json()['nsd'][0]
245 return scenario_check
248 class OpenmanoCliAPI(object):
249 """ This class implements the necessary funtionality to interact with """
253 def __init__(self
, log
, host
, port
, tenant
):
257 self
._tenant
= tenant
260 def openmano_cmd_path():
262 os
.environ
["RIFT_INSTALL"],
266 def _openmano_cmd(self
, arg_list
, expected_lines
=None):
267 cmd_args
= list(arg_list
)
268 cmd_args
.insert(0, self
.openmano_cmd_path())
271 "OPENMANO_HOST": self
._host
,
272 "OPENMANO_PORT": str(self
._port
),
273 "OPENMANO_TENANT": self
._tenant
,
277 "Running openmano command (%s) using env (%s)",
278 subprocess
.list2cmdline(cmd_args
),
282 proc
= subprocess
.Popen(
284 stdout
=subprocess
.PIPE
,
285 stderr
=subprocess
.PIPE
,
286 universal_newlines
=True,
290 stdout
, stderr
= proc
.communicate(timeout
=self
.CMD_TIMEOUT
)
291 except subprocess
.TimeoutExpired
:
292 self
._log
.error("Openmano command timed out")
294 stdout
, stderr
= proc
.communicate(timeout
=self
.CMD_TIMEOUT
)
296 if proc
.returncode
!= 0:
298 "Openmano command %s failed (rc=%s) with stdout: %s",
299 cmd_args
[1], proc
.returncode
, stdout
301 raise OpenmanoCommandFailed(stdout
)
303 self
._log
.debug("Openmano command completed with stdout: %s", stdout
)
305 output_lines
= stdout
.splitlines()
306 if expected_lines
is not None:
307 if len(output_lines
) != expected_lines
:
308 msg
= "Expected %s lines from openmano command. Got %s" % (expected_lines
, len(output_lines
))
310 raise OpenmanoUnexpectedOutput(msg
)
315 def vnf_create(self
, vnf_yaml_str
):
316 """ Create a Openmano VNF from a Openmano VNF YAML string """
318 self
._log
.debug("Creating VNF: %s", vnf_yaml_str
)
320 with tempfile
.NamedTemporaryFile() as vnf_file_hdl
:
321 vnf_file_hdl
.write(vnf_yaml_str
.encode())
325 output_lines
= self
._openmano
_cmd
(
326 ["vnf-create", vnf_file_hdl
.name
],
329 except OpenmanoCommandFailed
as e
:
330 if "already in use" in str(e
):
331 raise VNFExistsError("VNF was already added")
334 vnf_info_line
= output_lines
[0]
335 vnf_id
, vnf_name
= vnf_info_line
.split(" ", 1)
337 self
._log
.info("VNF %s Created: %s", vnf_name
, vnf_id
)
339 return vnf_id
, vnf_name
341 def vnf_delete(self
, vnf_uuid
):
343 ["vnf-delete", vnf_uuid
, "-f"],
346 self
._log
.info("VNF Deleted: %s", vnf_uuid
)
350 output_lines
= self
._openmano
_cmd
(
354 except OpenmanoCommandFailed
as e
:
355 self
._log
.warning("Vnf listing returned an error: %s", str(e
))
359 for line
in output_lines
:
361 uuid
, name
= line
.split(" ", 1)
362 name_uuid_map
[name
.strip()] = uuid
.strip()
364 self
._log
.debug("VNF list: {}".format(name_uuid_map
))
367 def ns_create(self
, ns_yaml_str
, name
=None):
368 self
._log
.info("Creating NS: %s", ns_yaml_str
)
370 with tempfile
.NamedTemporaryFile() as ns_file_hdl
:
371 ns_file_hdl
.write(ns_yaml_str
.encode())
374 cmd_args
= ["scenario-create", ns_file_hdl
.name
]
376 cmd_args
.extend(["--name", name
])
378 output_lines
= self
._openmano
_cmd
(
383 ns_info_line
= output_lines
[0]
384 ns_id
, ns_name
= ns_info_line
.split(" ", 1)
386 self
._log
.info("NS %s Created: %s", ns_name
, ns_id
)
388 return ns_id
, ns_name
391 self
._log
.debug("Getting NS list")
394 output_lines
= self
._openmano
_cmd
(
398 except OpenmanoCommandFailed
as e
:
399 self
._log
.warning("NS listing returned an error: %s", str(e
))
403 for line
in output_lines
:
405 uuid
, name
= line
.split(" ", 1)
406 name_uuid_map
[name
.strip()] = uuid
.strip()
408 self
._log
.debug("Scenario list: {}".format(name_uuid_map
))
411 def ns_delete(self
, ns_uuid
):
412 self
._log
.info("Deleting NS: %s", ns_uuid
)
415 ["scenario-delete", ns_uuid
, "-f"],
418 self
._log
.info("NS Deleted: %s", ns_uuid
)
420 def ns_instance_list(self
):
421 self
._log
.debug("Getting NS instance list")
424 output_lines
= self
._openmano
_cmd
(
425 ["instance-scenario-list"],
428 except OpenmanoCommandFailed
as e
:
429 self
._log
.warning("Instance scenario listing returned an error: %s", str(e
))
432 if "No scenario instances were found" in output_lines
[0]:
433 self
._log
.debug("No openmano instances were found")
437 for line
in output_lines
:
439 uuid
, name
= line
.split(" ", 1)
440 name_uuid_map
[name
.strip()] = uuid
.strip()
442 self
._log
.debug("Instance Scenario list: {}".format(name_uuid_map
))
445 def ns_instance_scenario_create(self
, instance_yaml_str
):
446 """ Create a Openmano NS instance from input YAML string """
448 self
._log
.debug("Instantiating instance: %s", instance_yaml_str
)
450 with tempfile
.NamedTemporaryFile() as ns_instance_file_hdl
:
451 ns_instance_file_hdl
.write(instance_yaml_str
.encode())
452 ns_instance_file_hdl
.flush()
455 output_lines
= self
._openmano
_cmd
(
456 ["instance-scenario-create", ns_instance_file_hdl
.name
],
459 except OpenmanoCommandFailed
as e
:
462 uuid
, _
= output_lines
[0].split(" ", 1)
464 self
._log
.info("NS Instance Created: %s", uuid
)
469 def ns_vim_network_create(self
, net_create_yaml_str
,datacenter_name
):
470 """ Create a Openmano VIM network from input YAML string """
472 self
._log
.debug("Creating VIM network instance: %s, DC %s", net_create_yaml_str
,datacenter_name
)
474 with tempfile
.NamedTemporaryFile() as net_create_file_hdl
:
475 net_create_file_hdl
.write(net_create_yaml_str
.encode())
476 net_create_file_hdl
.flush()
479 output_lines
= self
._openmano
_cmd
(
480 ["vim-net-create","--datacenter", datacenter_name
, net_create_file_hdl
.name
],
483 except OpenmanoCommandFailed
as e
:
486 uuid
, _
= output_lines
[0].split(" ", 1)
488 self
._log
.info("VIM Networks created in DC %s with ID: %s", datacenter_name
, uuid
)
492 def ns_vim_network_delete(self
, network_name
,datacenter_name
):
493 """ Delete a Openmano VIM network with given name """
495 self
._log
.debug("Deleting VIM network instance: %s, DC %s", network_name
,datacenter_name
)
497 output_lines
= self
._openmano
_cmd
(
498 ["vim-net-delete","--datacenter", datacenter_name
, network_name
],
501 except OpenmanoCommandFailed
as e
:
503 self
._log
.info("VIM Network deleted in DC %s with name: %s", datacenter_name
, network_name
)
506 def ns_instantiate(self
, scenario_name
, instance_name
, datacenter_name
=None):
508 "Instantiating NS %s using instance name %s",
513 cmd_args
= ["scenario-deploy", scenario_name
, instance_name
]
514 if datacenter_name
is not None:
515 cmd_args
.extend(["--datacenter", datacenter_name
])
517 output_lines
= self
._openmano
_cmd
(
522 uuid
, _
= output_lines
[0].split(" ", 1)
524 self
._log
.info("NS Instance Created: %s", uuid
)
528 def ns_terminate(self
, ns_instance_name
):
529 self
._log
.info("Terminating NS: %s", ns_instance_name
)
532 ["instance-scenario-delete", ns_instance_name
, "-f"],
535 self
._log
.info("NS Instance Deleted: %s", ns_instance_name
)
537 def datacenter_list(self
):
538 lines
= self
._openmano
_cmd
(["datacenter-list",])
540 # The results returned from openmano are formatted with whitespace and
541 # datacenter names may contain whitespace as well, so we use a regular
542 # expression to parse each line of the results return from openmano to
543 # extract the uuid and name of a datacenter.
545 uuid_pattern
= '(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)'.replace('x', hex)
546 name_pattern
= '(.+?)'
547 datacenter_regex
= re
.compile(r
'{uuid}\s+\b{name}\s*$'.format(
552 # Parse the results for the datacenter uuids and names
555 result
= datacenter_regex
.match(line
)
556 if result
is not None:
557 uuid
, name
= result
.groups()
558 datacenters
.append((uuid
, name
))
563 def valid_uuid(uuid_str
):
564 uuid_re
= re
.compile(
565 "^xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx$".replace('x', '[0-9a-fA-F]')
568 if not uuid_re
.match(uuid_str
):
569 raise argparse
.ArgumentTypeError("Got a valid uuid: %s" % uuid_str
)
574 def parse_args(argv
=sys
.argv
[1:]):
575 """ Parse the command line arguments
578 argv - The list of arguments to parse
581 Argparse Namespace instance
583 parser
= argparse
.ArgumentParser()
587 help="Openmano host/ip",
593 help="Openmano port",
600 help="Openmano tenant uuid to use",
603 subparsers
= parser
.add_subparsers(dest
='command', help='openmano commands')
605 vnf_create_parser
= subparsers
.add_parser(
607 help="Adds a openmano vnf into the catalog"
609 vnf_create_parser
.add_argument(
611 help="location of the JSON file describing the VNF",
612 type=argparse
.FileType('rb'),
615 vnf_delete_parser
= subparsers
.add_parser(
617 help="Deletes a openmano vnf into the catalog"
619 vnf_delete_parser
.add_argument(
621 help="The vnf to delete",
625 _
= subparsers
.add_parser(
627 help="List all the openmano VNFs in the catalog",
630 ns_create_parser
= subparsers
.add_parser(
632 help="Adds a openmano ns scenario into the catalog"
634 ns_create_parser
.add_argument(
636 help="location of the JSON file describing the NS",
637 type=argparse
.FileType('rb'),
640 ns_delete_parser
= subparsers
.add_parser(
642 help="Deletes a openmano ns into the catalog"
644 ns_delete_parser
.add_argument(
646 help="The ns to delete",
650 _
= subparsers
.add_parser(
652 help="List all the openmano scenarios in the catalog",
655 ns_instance_create_parser
= subparsers
.add_parser(
657 help="Deploys a openmano ns scenario into the catalog"
659 ns_instance_create_parser
.add_argument(
661 help="The ns scenario name to deploy",
663 ns_instance_create_parser
.add_argument(
665 help="The ns instance name to deploy",
669 ns_instance_delete_parser
= subparsers
.add_parser(
670 'instance-scenario-delete',
671 help="Deploys a openmano ns scenario into the catalog"
673 ns_instance_delete_parser
.add_argument(
675 help="The ns instance name to delete",
679 _
= subparsers
.add_parser(
680 'instance-scenario-list',
681 help="List all the openmano scenario instances in the catalog",
684 _
= subparsers
.add_parser(
686 help="List all the openmano datacenters",
689 args
= parser
.parse_args(argv
)
695 logging
.basicConfig(level
=logging
.DEBUG
)
696 logger
= logging
.getLogger("openmano_client.py")
698 if "RIFT_INSTALL" not in os
.environ
:
699 logger
.error("Must be in rift-shell to run.")
703 openmano_cli
= OpenmanoCliAPI(logger
, args
.host
, args
.port
, args
.tenant
)
705 if args
.command
== "vnf-create":
706 openmano_cli
.vnf_create(args
.file.read())
708 elif args
.command
== "vnf-delete":
709 openmano_cli
.vnf_delete(args
.uuid
)
711 elif args
.command
== "vnf-list":
712 for uuid
, name
in openmano_cli
.vnf_list().items():
713 print("{} {}".format(uuid
, name
))
715 elif args
.command
== "scenario-create":
716 openmano_cli
.ns_create(args
.file.read())
718 elif args
.command
== "scenario-delete":
719 openmano_cli
.ns_delete(args
.uuid
)
721 elif args
.command
== "scenario-list":
722 for uuid
, name
in openmano_cli
.ns_list().items():
723 print("{} {}".format(uuid
, name
))
725 elif args
.command
== "scenario-deploy":
726 openmano_cli
.ns_instantiate(args
.scenario_name
, args
.instance_name
)
728 elif args
.command
== "instance-scenario-delete":
729 openmano_cli
.ns_terminate(args
.instance_name
)
731 elif args
.command
== "instance-scenario-list":
732 for uuid
, name
in openmano_cli
.ns_instance_list().items():
733 print("{} {}".format(uuid
, name
))
735 elif args
.command
== "datacenter-list":
736 for uuid
, name
in openmano_cli
.datacenter_list():
737 print("{} {}".format(uuid
, name
))
740 logger
.error("Unknown command: %s", args
.command
)
743 if __name__
== "__main__":