Revert "Full Juju Charm support"
[osm/SO.git] / models / openmano / python / rift / openmano / openmano_client.py
1 #!/usr/bin/python3
2
3 #
4 # Copyright 2016 RIFT.IO Inc
5 #
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
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
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.
17 #
18
19 import argparse
20 import logging
21 import os
22 import re
23 import subprocess
24 import sys
25 import tempfile
26 import requests
27 import json
28
29
30 class OpenmanoCommandFailed(Exception):
31 pass
32
33
34 class OpenmanoUnexpectedOutput(Exception):
35 pass
36
37
38 class VNFExistsError(Exception):
39 pass
40
41
42 class InstanceStatusError(Exception):
43 pass
44
45
46 class OpenmanoHttpAPI(object):
47 def __init__(self, log, host, port, tenant):
48 self._log = log
49 self._host = host
50 self._port = port
51 self._tenant = tenant
52
53 self._session = requests.Session()
54
55 def instances(self):
56 url = "http://{host}:{port}/openmano/{tenant}/instances".format(
57 host=self._host,
58 port=self._port,
59 tenant=self._tenant,
60 )
61
62 resp = self._session.get(url)
63 try:
64 resp.raise_for_status()
65 except requests.exceptions.HTTPError as e:
66 raise InstanceStatusError(e)
67
68 return resp.json()["instances"]
69
70 def get_instance(self, instance_uuid):
71 url = "http://{host}:{port}/openmano/{tenant}/instances/{instance}".format(
72 host=self._host,
73 port=self._port,
74 tenant=self._tenant,
75 instance=instance_uuid,
76 )
77
78 resp = self._session.get(url)
79 try:
80 resp.raise_for_status()
81 except requests.exceptions.HTTPError as e:
82 raise InstanceStatusError(e)
83
84 return resp.json()
85
86 def get_instance_vm_console_url(self, instance_uuid, vim_uuid):
87 url = "http://{host}:{port}/openmano/{tenant}/instances/{instance}/action".format(
88 host=self._host,
89 port=self._port,
90 tenant=self._tenant,
91 instance=instance_uuid,
92 )
93
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'},
99 data=payload_data)
100 try:
101 resp.raise_for_status()
102 except requests.exceptions.HTTPError as e:
103 raise InstanceStatusError(e)
104 result = resp.json()
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"]
107
108 return None
109
110 def vnfs(self):
111 url = "http://{host}:{port}/openmano/{tenant}/vnfs".format(
112 host=self._host,
113 port=self._port,
114 tenant=self._tenant
115 )
116 resp = self._session.get(url, headers={'content-type': 'application/json'})
117
118 try:
119 resp.raise_for_status()
120 except requests.exceptions.HTTPError as e:
121 raise InstanceStatusError(e)
122
123 return resp.json()
124
125 def vnf(self, vnf_id):
126 vnf_uuid = None
127 try:
128 vnfs = self.vnfs()
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"]
133 break
134 except Exception as e:
135 raise e
136
137 if not vnf_uuid:
138 return None
139 else:
140 url = "http://{host}:{port}/openmano/{tenant}/vnfs/{uuid}".format(
141 host=self._host,
142 port=self._port,
143 tenant=self._tenant,
144 uuid=vnf_uuid
145 )
146 resp = self._session.get(url, headers={'content-type': 'application/json'})
147
148 try:
149 resp.raise_for_status()
150 except requests.exceptions.HTTPError as e:
151 raise InstanceStatusError(e)
152
153 return resp.json()['vnf']
154
155 def scenarios(self):
156 url = "http://{host}:{port}/openmano/{tenant}/scenarios".format(
157 host=self._host,
158 port=self._port,
159 tenant=self._tenant
160 )
161 resp = self._session.get(url, headers={'content-type': 'application/json'})
162
163 try:
164 resp.raise_for_status()
165 except requests.exceptions.HTTPError as e:
166 raise InstanceStatusError(e)
167
168 return resp.json()
169
170 def scenario(self, scenario_id):
171 scenario_uuid = None
172 try:
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"]
178 break
179 except Exception as e:
180 raise e
181
182 if not scenario_uuid:
183 return None
184 else:
185 url = "http://{host}:{port}/openmano/{tenant}/scenarios/{uuid}".format(
186 host=self._host,
187 port=self._port,
188 tenant=self._tenant,
189 uuid=scenario_uuid
190 )
191 resp = self._session.get(url, headers={'content-type': 'application/json'})
192
193 try:
194 resp.raise_for_status()
195 except requests.exceptions.HTTPError as e:
196 raise InstanceStatusError(e)
197
198 return resp.json()['scenario']
199
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)
204
205 if not vnf_check:
206 url = "http://{host}:{port}/openmano/v3/{tenant}/vnfd".format(
207 host=self._host,
208 port=self._port,
209 tenant=self._tenant
210 )
211 payload_data = json.dumps(vnfd_body)
212 resp = self._session.post(url, headers={'content-type': 'application/json'},
213 data=payload_data)
214 try:
215 resp.raise_for_status()
216 except requests.exceptions.HTTPError as e:
217 raise InstanceStatusError(e)
218
219 return resp.json()['vnfd'][0]
220
221 else:
222 return vnf_check
223
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)
228
229 if not scenario_check:
230 url = "http://{host}:{port}/openmano/v3/{tenant}/nsd".format(
231 host=self._host,
232 port=self._port,
233 tenant=self._tenant
234 )
235 payload_data = json.dumps(nsd_body)
236 resp = self._session.post(url, headers={'content-type': 'application/json'},
237 data=payload_data)
238 try:
239 resp.raise_for_status()
240 except requests.exceptions.HTTPError as e:
241 raise InstanceStatusError(e)
242
243 return resp.json()['nsd'][0]
244 else:
245 return scenario_check
246
247
248 class OpenmanoCliAPI(object):
249 """ This class implements the necessary funtionality to interact with """
250
251 CMD_TIMEOUT = 120
252
253 def __init__(self, log, host, port, tenant):
254 self._log = log
255 self._host = host
256 self._port = port
257 self._tenant = tenant
258
259 @staticmethod
260 def openmano_cmd_path():
261 return os.path.join(
262 os.environ["RIFT_INSTALL"],
263 "usr/bin/openmano"
264 )
265
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())
269
270 env = {
271 "OPENMANO_HOST": self._host,
272 "OPENMANO_PORT": str(self._port),
273 "OPENMANO_TENANT": self._tenant,
274 }
275
276 self._log.debug(
277 "Running openmano command (%s) using env (%s)",
278 subprocess.list2cmdline(cmd_args),
279 env,
280 )
281
282 proc = subprocess.Popen(
283 cmd_args,
284 stdout=subprocess.PIPE,
285 stderr=subprocess.PIPE,
286 universal_newlines=True,
287 env=env
288 )
289 try:
290 stdout, stderr = proc.communicate(timeout=self.CMD_TIMEOUT)
291 except subprocess.TimeoutExpired:
292 self._log.error("Openmano command timed out")
293 proc.terminate()
294 stdout, stderr = proc.communicate(timeout=self.CMD_TIMEOUT)
295
296 if proc.returncode != 0:
297 self._log.error(
298 "Openmano command %s failed (rc=%s) with stdout: %s",
299 cmd_args[1], proc.returncode, stdout
300 )
301 raise OpenmanoCommandFailed(stdout)
302
303 self._log.debug("Openmano command completed with stdout: %s", stdout)
304
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))
309 self._log.error(msg)
310 raise OpenmanoUnexpectedOutput(msg)
311
312 return output_lines
313
314
315 def vnf_create(self, vnf_yaml_str):
316 """ Create a Openmano VNF from a Openmano VNF YAML string """
317
318 self._log.debug("Creating VNF: %s", vnf_yaml_str)
319
320 with tempfile.NamedTemporaryFile() as vnf_file_hdl:
321 vnf_file_hdl.write(vnf_yaml_str.encode())
322 vnf_file_hdl.flush()
323
324 try:
325 output_lines = self._openmano_cmd(
326 ["vnf-create", vnf_file_hdl.name],
327 expected_lines=1
328 )
329 except OpenmanoCommandFailed as e:
330 if "already in use" in str(e):
331 raise VNFExistsError("VNF was already added")
332 raise
333
334 vnf_info_line = output_lines[0]
335 vnf_id, vnf_name = vnf_info_line.split(" ", 1)
336
337 self._log.info("VNF %s Created: %s", vnf_name, vnf_id)
338
339 return vnf_id, vnf_name
340
341 def vnf_delete(self, vnf_uuid):
342 self._openmano_cmd(
343 ["vnf-delete", vnf_uuid, "-f"],
344 )
345
346 self._log.info("VNF Deleted: %s", vnf_uuid)
347
348 def vnf_list(self):
349 try:
350 output_lines = self._openmano_cmd(
351 ["vnf-list"],
352 )
353
354 except OpenmanoCommandFailed as e:
355 self._log.warning("Vnf listing returned an error: %s", str(e))
356 return {}
357
358 name_uuid_map = {}
359 for line in output_lines:
360 line = line.strip()
361 uuid, name = line.split(" ", 1)
362 name_uuid_map[name.strip()] = uuid.strip()
363
364 self._log.debug("VNF list: {}".format(name_uuid_map))
365 return name_uuid_map
366
367 def ns_create(self, ns_yaml_str, name=None):
368 self._log.info("Creating NS: %s", ns_yaml_str)
369
370 with tempfile.NamedTemporaryFile() as ns_file_hdl:
371 ns_file_hdl.write(ns_yaml_str.encode())
372 ns_file_hdl.flush()
373
374 cmd_args = ["scenario-create", ns_file_hdl.name]
375 if name is not None:
376 cmd_args.extend(["--name", name])
377
378 output_lines = self._openmano_cmd(
379 cmd_args,
380 expected_lines=1
381 )
382
383 ns_info_line = output_lines[0]
384 ns_id, ns_name = ns_info_line.split(" ", 1)
385
386 self._log.info("NS %s Created: %s", ns_name, ns_id)
387
388 return ns_id, ns_name
389
390 def ns_list(self):
391 self._log.debug("Getting NS list")
392
393 try:
394 output_lines = self._openmano_cmd(
395 ["scenario-list"],
396 )
397
398 except OpenmanoCommandFailed as e:
399 self._log.warning("NS listing returned an error: %s", str(e))
400 return {}
401
402 name_uuid_map = {}
403 for line in output_lines:
404 line = line.strip()
405 uuid, name = line.split(" ", 1)
406 name_uuid_map[name.strip()] = uuid.strip()
407
408 self._log.debug("Scenario list: {}".format(name_uuid_map))
409 return name_uuid_map
410
411 def ns_delete(self, ns_uuid):
412 self._log.info("Deleting NS: %s", ns_uuid)
413
414 self._openmano_cmd(
415 ["scenario-delete", ns_uuid, "-f"],
416 )
417
418 self._log.info("NS Deleted: %s", ns_uuid)
419
420 def ns_instance_list(self):
421 self._log.debug("Getting NS instance list")
422
423 try:
424 output_lines = self._openmano_cmd(
425 ["instance-scenario-list"],
426 )
427
428 except OpenmanoCommandFailed as e:
429 self._log.warning("Instance scenario listing returned an error: %s", str(e))
430 return {}
431
432 if "No scenario instances were found" in output_lines[0]:
433 self._log.debug("No openmano instances were found")
434 return {}
435
436 name_uuid_map = {}
437 for line in output_lines:
438 line = line.strip()
439 uuid, name = line.split(" ", 1)
440 name_uuid_map[name.strip()] = uuid.strip()
441
442 self._log.debug("Instance Scenario list: {}".format(name_uuid_map))
443 return name_uuid_map
444
445 def ns_instance_scenario_create(self, instance_yaml_str):
446 """ Create a Openmano NS instance from input YAML string """
447
448 self._log.debug("Instantiating instance: %s", instance_yaml_str)
449
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()
453
454 try:
455 output_lines = self._openmano_cmd(
456 ["instance-scenario-create", ns_instance_file_hdl.name],
457 expected_lines=1
458 )
459 except OpenmanoCommandFailed as e:
460 raise
461
462 uuid, _ = output_lines[0].split(" ", 1)
463
464 self._log.info("NS Instance Created: %s", uuid)
465
466 return uuid
467
468
469 def ns_vim_network_create(self, net_create_yaml_str,datacenter_name):
470 """ Create a Openmano VIM network from input YAML string """
471
472 self._log.debug("Creating VIM network instance: %s, DC %s", net_create_yaml_str,datacenter_name)
473
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()
477
478 try:
479 output_lines = self._openmano_cmd(
480 ["vim-net-create","--datacenter", datacenter_name, net_create_file_hdl.name],
481 expected_lines=1
482 )
483 except OpenmanoCommandFailed as e:
484 raise
485
486 uuid, _ = output_lines[0].split(" ", 1)
487
488 self._log.info("VIM Networks created in DC %s with ID: %s", datacenter_name, uuid)
489
490 return uuid
491
492 def ns_vim_network_delete(self, network_name,datacenter_name):
493 """ Delete a Openmano VIM network with given name """
494
495 self._log.debug("Deleting VIM network instance: %s, DC %s", network_name,datacenter_name)
496 try:
497 output_lines = self._openmano_cmd(
498 ["vim-net-delete","--datacenter", datacenter_name, network_name],
499 expected_lines=1
500 )
501 except OpenmanoCommandFailed as e:
502 raise
503 self._log.info("VIM Network deleted in DC %s with name: %s", datacenter_name, network_name)
504
505
506 def ns_instantiate(self, scenario_name, instance_name, datacenter_name=None):
507 self._log.info(
508 "Instantiating NS %s using instance name %s",
509 scenario_name,
510 instance_name,
511 )
512
513 cmd_args = ["scenario-deploy", scenario_name, instance_name]
514 if datacenter_name is not None:
515 cmd_args.extend(["--datacenter", datacenter_name])
516
517 output_lines = self._openmano_cmd(
518 cmd_args,
519 expected_lines=4
520 )
521
522 uuid, _ = output_lines[0].split(" ", 1)
523
524 self._log.info("NS Instance Created: %s", uuid)
525
526 return uuid
527
528 def ns_terminate(self, ns_instance_name):
529 self._log.info("Terminating NS: %s", ns_instance_name)
530
531 self._openmano_cmd(
532 ["instance-scenario-delete", ns_instance_name, "-f"],
533 )
534
535 self._log.info("NS Instance Deleted: %s", ns_instance_name)
536
537 def datacenter_list(self):
538 lines = self._openmano_cmd(["datacenter-list",])
539
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.
544 hex = '[0-9a-fA-F]'
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(
548 uuid=uuid_pattern,
549 name=name_pattern,
550 ))
551
552 # Parse the results for the datacenter uuids and names
553 datacenters = list()
554 for line in lines:
555 result = datacenter_regex.match(line)
556 if result is not None:
557 uuid, name = result.groups()
558 datacenters.append((uuid, name))
559
560 return datacenters
561
562
563 def valid_uuid(uuid_str):
564 uuid_re = re.compile(
565 "^xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx$".replace('x', '[0-9a-fA-F]')
566 )
567
568 if not uuid_re.match(uuid_str):
569 raise argparse.ArgumentTypeError("Got a valid uuid: %s" % uuid_str)
570
571 return uuid_str
572
573
574 def parse_args(argv=sys.argv[1:]):
575 """ Parse the command line arguments
576
577 Arguments:
578 argv - The list of arguments to parse
579
580 Returns:
581 Argparse Namespace instance
582 """
583 parser = argparse.ArgumentParser()
584 parser.add_argument(
585 '-d', '--host',
586 default='localhost',
587 help="Openmano host/ip",
588 )
589
590 parser.add_argument(
591 '-p', '--port',
592 default='9090',
593 help="Openmano port",
594 )
595
596 parser.add_argument(
597 '-t', '--tenant',
598 required=True,
599 type=valid_uuid,
600 help="Openmano tenant uuid to use",
601 )
602
603 subparsers = parser.add_subparsers(dest='command', help='openmano commands')
604
605 vnf_create_parser = subparsers.add_parser(
606 'vnf-create',
607 help="Adds a openmano vnf into the catalog"
608 )
609 vnf_create_parser.add_argument(
610 "file",
611 help="location of the JSON file describing the VNF",
612 type=argparse.FileType('rb'),
613 )
614
615 vnf_delete_parser = subparsers.add_parser(
616 'vnf-delete',
617 help="Deletes a openmano vnf into the catalog"
618 )
619 vnf_delete_parser.add_argument(
620 "uuid",
621 help="The vnf to delete",
622 type=valid_uuid,
623 )
624
625 _ = subparsers.add_parser(
626 'vnf-list',
627 help="List all the openmano VNFs in the catalog",
628 )
629
630 ns_create_parser = subparsers.add_parser(
631 'scenario-create',
632 help="Adds a openmano ns scenario into the catalog"
633 )
634 ns_create_parser.add_argument(
635 "file",
636 help="location of the JSON file describing the NS",
637 type=argparse.FileType('rb'),
638 )
639
640 ns_delete_parser = subparsers.add_parser(
641 'scenario-delete',
642 help="Deletes a openmano ns into the catalog"
643 )
644 ns_delete_parser.add_argument(
645 "uuid",
646 help="The ns to delete",
647 type=valid_uuid,
648 )
649
650 _ = subparsers.add_parser(
651 'scenario-list',
652 help="List all the openmano scenarios in the catalog",
653 )
654
655 ns_instance_create_parser = subparsers.add_parser(
656 'scenario-deploy',
657 help="Deploys a openmano ns scenario into the catalog"
658 )
659 ns_instance_create_parser.add_argument(
660 "scenario_name",
661 help="The ns scenario name to deploy",
662 )
663 ns_instance_create_parser.add_argument(
664 "instance_name",
665 help="The ns instance name to deploy",
666 )
667
668
669 ns_instance_delete_parser = subparsers.add_parser(
670 'instance-scenario-delete',
671 help="Deploys a openmano ns scenario into the catalog"
672 )
673 ns_instance_delete_parser.add_argument(
674 "instance_name",
675 help="The ns instance name to delete",
676 )
677
678
679 _ = subparsers.add_parser(
680 'instance-scenario-list',
681 help="List all the openmano scenario instances in the catalog",
682 )
683
684 _ = subparsers.add_parser(
685 'datacenter-list',
686 help="List all the openmano datacenters",
687 )
688
689 args = parser.parse_args(argv)
690
691 return args
692
693
694 def main():
695 logging.basicConfig(level=logging.DEBUG)
696 logger = logging.getLogger("openmano_client.py")
697
698 if "RIFT_INSTALL" not in os.environ:
699 logger.error("Must be in rift-shell to run.")
700 sys.exit(1)
701
702 args = parse_args()
703 openmano_cli = OpenmanoCliAPI(logger, args.host, args.port, args.tenant)
704
705 if args.command == "vnf-create":
706 openmano_cli.vnf_create(args.file.read())
707
708 elif args.command == "vnf-delete":
709 openmano_cli.vnf_delete(args.uuid)
710
711 elif args.command == "vnf-list":
712 for uuid, name in openmano_cli.vnf_list().items():
713 print("{} {}".format(uuid, name))
714
715 elif args.command == "scenario-create":
716 openmano_cli.ns_create(args.file.read())
717
718 elif args.command == "scenario-delete":
719 openmano_cli.ns_delete(args.uuid)
720
721 elif args.command == "scenario-list":
722 for uuid, name in openmano_cli.ns_list().items():
723 print("{} {}".format(uuid, name))
724
725 elif args.command == "scenario-deploy":
726 openmano_cli.ns_instantiate(args.scenario_name, args.instance_name)
727
728 elif args.command == "instance-scenario-delete":
729 openmano_cli.ns_terminate(args.instance_name)
730
731 elif args.command == "instance-scenario-list":
732 for uuid, name in openmano_cli.ns_instance_list().items():
733 print("{} {}".format(uuid, name))
734
735 elif args.command == "datacenter-list":
736 for uuid, name in openmano_cli.datacenter_list():
737 print("{} {}".format(uuid, name))
738
739 else:
740 logger.error("Unknown command: %s", args.command)
741 sys.exit(1)
742
743 if __name__ == "__main__":
744 main()