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