Bug 160 Fix SO restart with running NS instances
[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
96 class OpenmanoCliAPI(object):
97 """ This class implements the necessary funtionality to interact with """
98
99 CMD_TIMEOUT = 30
100
101 def __init__(self, log, host, port, tenant):
102 self._log = log
103 self._host = host
104 self._port = port
105 self._tenant = tenant
106
107 @staticmethod
108 def openmano_cmd_path():
109 return os.path.join(
110 os.environ["RIFT_INSTALL"],
111 "usr/bin/openmano"
112 )
113
114 def _openmano_cmd(self, arg_list, expected_lines=None):
115 cmd_args = list(arg_list)
116 cmd_args.insert(0, self.openmano_cmd_path())
117
118 env = {
119 "OPENMANO_HOST": self._host,
120 "OPENMANO_PORT": str(self._port),
121 "OPENMANO_TENANT": self._tenant,
122 }
123
124 self._log.debug(
125 "Running openmano command (%s) using env (%s)",
126 subprocess.list2cmdline(cmd_args),
127 env,
128 )
129
130 proc = subprocess.Popen(
131 cmd_args,
132 stdout=subprocess.PIPE,
133 stderr=subprocess.PIPE,
134 universal_newlines=True,
135 env=env
136 )
137 try:
138 stdout, stderr = proc.communicate(timeout=self.CMD_TIMEOUT)
139 except subprocess.TimeoutExpired:
140 self._log.error("Openmano command timed out")
141 proc.terminate()
142 stdout, stderr = proc.communicate(timeout=self.CMD_TIMEOUT)
143
144 if proc.returncode != 0:
145 self._log.error(
146 "Openmano command failed (rc=%s) with stdout: %s",
147 proc.returncode, stdout
148 )
149 raise OpenmanoCommandFailed(stdout)
150
151 self._log.debug("Openmano command completed with stdout: %s", stdout)
152
153 output_lines = stdout.splitlines()
154 if expected_lines is not None:
155 if len(output_lines) != expected_lines:
156 msg = "Expected %s lines from openmano command. Got %s" % (expected_lines, len(output_lines))
157 self._log.error(msg)
158 raise OpenmanoUnexpectedOutput(msg)
159
160 return output_lines
161
162
163 def vnf_create(self, vnf_yaml_str):
164 """ Create a Openmano VNF from a Openmano VNF YAML string """
165
166 self._log.debug("Creating VNF: %s", vnf_yaml_str)
167
168 with tempfile.NamedTemporaryFile() as vnf_file_hdl:
169 vnf_file_hdl.write(vnf_yaml_str.encode())
170 vnf_file_hdl.flush()
171
172 try:
173 output_lines = self._openmano_cmd(
174 ["vnf-create", vnf_file_hdl.name],
175 expected_lines=1
176 )
177 except OpenmanoCommandFailed as e:
178 if "already in use" in str(e):
179 raise VNFExistsError("VNF was already added")
180 raise
181
182 vnf_info_line = output_lines[0]
183 vnf_id, vnf_name = vnf_info_line.split(" ", 1)
184
185 self._log.info("VNF %s Created: %s", vnf_name, vnf_id)
186
187 return vnf_id, vnf_name
188
189 def vnf_delete(self, vnf_uuid):
190 self._openmano_cmd(
191 ["vnf-delete", vnf_uuid, "-f"],
192 )
193
194 self._log.info("VNF Deleted: %s", vnf_uuid)
195
196 def vnf_list(self):
197 try:
198 output_lines = self._openmano_cmd(
199 ["vnf-list"],
200 )
201
202 except OpenmanoCommandFailed as e:
203 self._log.warning("Vnf listing returned an error: %s", str(e))
204 return {}
205
206 name_uuid_map = {}
207 for line in output_lines:
208 line = line.strip()
209 uuid, name = line.split(" ", 1)
210 name_uuid_map[name.strip()] = uuid.strip()
211
212 self._log.debug("VNF list: {}".format(name_uuid_map))
213 return name_uuid_map
214
215 def ns_create(self, ns_yaml_str, name=None):
216 self._log.info("Creating NS: %s", ns_yaml_str)
217
218 with tempfile.NamedTemporaryFile() as ns_file_hdl:
219 ns_file_hdl.write(ns_yaml_str.encode())
220 ns_file_hdl.flush()
221
222 cmd_args = ["scenario-create", ns_file_hdl.name]
223 if name is not None:
224 cmd_args.extend(["--name", name])
225
226 output_lines = self._openmano_cmd(
227 cmd_args,
228 expected_lines=1
229 )
230
231 ns_info_line = output_lines[0]
232 ns_id, ns_name = ns_info_line.split(" ", 1)
233
234 self._log.info("NS %s Created: %s", ns_name, ns_id)
235
236 return ns_id, ns_name
237
238 def ns_list(self):
239 self._log.debug("Getting NS list")
240
241 try:
242 output_lines = self._openmano_cmd(
243 ["scenario-list"],
244 )
245
246 except OpenmanoCommandFailed as e:
247 self._log.warning("NS listing returned an error: %s", str(e))
248 return {}
249
250 name_uuid_map = {}
251 for line in output_lines:
252 line = line.strip()
253 uuid, name = line.split(" ", 1)
254 name_uuid_map[name.strip()] = uuid.strip()
255
256 self._log.debug("Scenario list: {}".format(name_uuid_map))
257 return name_uuid_map
258
259 def ns_delete(self, ns_uuid):
260 self._log.info("Deleting NS: %s", ns_uuid)
261
262 self._openmano_cmd(
263 ["scenario-delete", ns_uuid, "-f"],
264 )
265
266 self._log.info("NS Deleted: %s", ns_uuid)
267
268 def ns_instance_list(self):
269 self._log.debug("Getting NS instance list")
270
271 try:
272 output_lines = self._openmano_cmd(
273 ["instance-scenario-list"],
274 )
275
276 except OpenmanoCommandFailed as e:
277 self._log.warning("Instance scenario listing returned an error: %s", str(e))
278 return {}
279
280 if "No scenario instances were found" in output_lines[0]:
281 self._log.debug("No openmano instances were found")
282 return {}
283
284 name_uuid_map = {}
285 for line in output_lines:
286 line = line.strip()
287 uuid, name = line.split(" ", 1)
288 name_uuid_map[name.strip()] = uuid.strip()
289
290 self._log.debug("Instance Scenario list: {}".format(name_uuid_map))
291 return name_uuid_map
292
293 def ns_instance_scenario_create(self, instance_yaml_str):
294 """ Create a Openmano NS instance from input YAML string """
295
296 self._log.debug("Instantiating instance: %s", instance_yaml_str)
297
298 with tempfile.NamedTemporaryFile() as ns_instance_file_hdl:
299 ns_instance_file_hdl.write(instance_yaml_str.encode())
300 ns_instance_file_hdl.flush()
301
302 try:
303 output_lines = self._openmano_cmd(
304 ["instance-scenario-create", ns_instance_file_hdl.name],
305 expected_lines=1
306 )
307 except OpenmanoCommandFailed as e:
308 raise
309
310 uuid, _ = output_lines[0].split(" ", 1)
311
312 self._log.info("NS Instance Created: %s", uuid)
313
314 return uuid
315
316
317 def ns_vim_network_create(self, net_create_yaml_str,datacenter_name):
318 """ Create a Openmano VIM network from input YAML string """
319
320 self._log.debug("Creating VIM network instance: %s, DC %s", net_create_yaml_str,datacenter_name)
321
322 with tempfile.NamedTemporaryFile() as net_create_file_hdl:
323 net_create_file_hdl.write(net_create_yaml_str.encode())
324 net_create_file_hdl.flush()
325
326 try:
327 output_lines = self._openmano_cmd(
328 ["vim-net-create","--datacenter", datacenter_name, net_create_file_hdl.name],
329 expected_lines=1
330 )
331 except OpenmanoCommandFailed as e:
332 raise
333
334 uuid, _ = output_lines[0].split(" ", 1)
335
336 self._log.info("VIM Networks created in DC %s with ID: %s", datacenter_name, uuid)
337
338 return uuid
339
340 def ns_vim_network_delete(self, network_name,datacenter_name):
341 """ Delete a Openmano VIM network with given name """
342
343 self._log.debug("Deleting VIM network instance: %s, DC %s", network_name,datacenter_name)
344 try:
345 output_lines = self._openmano_cmd(
346 ["vim-net-delete","--datacenter", datacenter_name, network_name],
347 expected_lines=1
348 )
349 except OpenmanoCommandFailed as e:
350 raise
351 self._log.info("VIM Network deleted in DC %s with name: %s", datacenter_name, network_name)
352
353
354 def ns_instantiate(self, scenario_name, instance_name, datacenter_name=None):
355 self._log.info(
356 "Instantiating NS %s using instance name %s",
357 scenario_name,
358 instance_name,
359 )
360
361 cmd_args = ["scenario-deploy", scenario_name, instance_name]
362 if datacenter_name is not None:
363 cmd_args.extend(["--datacenter", datacenter_name])
364
365 output_lines = self._openmano_cmd(
366 cmd_args,
367 expected_lines=4
368 )
369
370 uuid, _ = output_lines[0].split(" ", 1)
371
372 self._log.info("NS Instance Created: %s", uuid)
373
374 return uuid
375
376 def ns_terminate(self, ns_instance_name):
377 self._log.info("Terminating NS: %s", ns_instance_name)
378
379 self._openmano_cmd(
380 ["instance-scenario-delete", ns_instance_name, "-f"],
381 )
382
383 self._log.info("NS Instance Deleted: %s", ns_instance_name)
384
385 def datacenter_list(self):
386 lines = self._openmano_cmd(["datacenter-list",])
387
388 # The results returned from openmano are formatted with whitespace and
389 # datacenter names may contain whitespace as well, so we use a regular
390 # expression to parse each line of the results return from openmano to
391 # extract the uuid and name of a datacenter.
392 hex = '[0-9a-fA-F]'
393 uuid_pattern = '(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)'.replace('x', hex)
394 name_pattern = '(.+?)'
395 datacenter_regex = re.compile(r'{uuid}\s+\b{name}\s*$'.format(
396 uuid=uuid_pattern,
397 name=name_pattern,
398 ))
399
400 # Parse the results for the datacenter uuids and names
401 datacenters = list()
402 for line in lines:
403 result = datacenter_regex.match(line)
404 if result is not None:
405 uuid, name = result.groups()
406 datacenters.append((uuid, name))
407
408 return datacenters
409
410
411 def valid_uuid(uuid_str):
412 uuid_re = re.compile(
413 "^xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx$".replace('x', '[0-9a-fA-F]')
414 )
415
416 if not uuid_re.match(uuid_str):
417 raise argparse.ArgumentTypeError("Got a valid uuid: %s" % uuid_str)
418
419 return uuid_str
420
421
422 def parse_args(argv=sys.argv[1:]):
423 """ Parse the command line arguments
424
425 Arguments:
426 argv - The list of arguments to parse
427
428 Returns:
429 Argparse Namespace instance
430 """
431 parser = argparse.ArgumentParser()
432 parser.add_argument(
433 '-d', '--host',
434 default='localhost',
435 help="Openmano host/ip",
436 )
437
438 parser.add_argument(
439 '-p', '--port',
440 default='9090',
441 help="Openmano port",
442 )
443
444 parser.add_argument(
445 '-t', '--tenant',
446 required=True,
447 type=valid_uuid,
448 help="Openmano tenant uuid to use",
449 )
450
451 subparsers = parser.add_subparsers(dest='command', help='openmano commands')
452
453 vnf_create_parser = subparsers.add_parser(
454 'vnf-create',
455 help="Adds a openmano vnf into the catalog"
456 )
457 vnf_create_parser.add_argument(
458 "file",
459 help="location of the JSON file describing the VNF",
460 type=argparse.FileType('rb'),
461 )
462
463 vnf_delete_parser = subparsers.add_parser(
464 'vnf-delete',
465 help="Deletes a openmano vnf into the catalog"
466 )
467 vnf_delete_parser.add_argument(
468 "uuid",
469 help="The vnf to delete",
470 type=valid_uuid,
471 )
472
473 _ = subparsers.add_parser(
474 'vnf-list',
475 help="List all the openmano VNFs in the catalog",
476 )
477
478 ns_create_parser = subparsers.add_parser(
479 'scenario-create',
480 help="Adds a openmano ns scenario into the catalog"
481 )
482 ns_create_parser.add_argument(
483 "file",
484 help="location of the JSON file describing the NS",
485 type=argparse.FileType('rb'),
486 )
487
488 ns_delete_parser = subparsers.add_parser(
489 'scenario-delete',
490 help="Deletes a openmano ns into the catalog"
491 )
492 ns_delete_parser.add_argument(
493 "uuid",
494 help="The ns to delete",
495 type=valid_uuid,
496 )
497
498 _ = subparsers.add_parser(
499 'scenario-list',
500 help="List all the openmano scenarios in the catalog",
501 )
502
503 ns_instance_create_parser = subparsers.add_parser(
504 'scenario-deploy',
505 help="Deploys a openmano ns scenario into the catalog"
506 )
507 ns_instance_create_parser.add_argument(
508 "scenario_name",
509 help="The ns scenario name to deploy",
510 )
511 ns_instance_create_parser.add_argument(
512 "instance_name",
513 help="The ns instance name to deploy",
514 )
515
516
517 ns_instance_delete_parser = subparsers.add_parser(
518 'instance-scenario-delete',
519 help="Deploys a openmano ns scenario into the catalog"
520 )
521 ns_instance_delete_parser.add_argument(
522 "instance_name",
523 help="The ns instance name to delete",
524 )
525
526
527 _ = subparsers.add_parser(
528 'instance-scenario-list',
529 help="List all the openmano scenario instances in the catalog",
530 )
531
532 _ = subparsers.add_parser(
533 'datacenter-list',
534 help="List all the openmano datacenters",
535 )
536
537 args = parser.parse_args(argv)
538
539 return args
540
541
542 def main():
543 logging.basicConfig(level=logging.DEBUG)
544 logger = logging.getLogger("openmano_client.py")
545
546 if "RIFT_INSTALL" not in os.environ:
547 logger.error("Must be in rift-shell to run.")
548 sys.exit(1)
549
550 args = parse_args()
551 openmano_cli = OpenmanoCliAPI(logger, args.host, args.port, args.tenant)
552
553 if args.command == "vnf-create":
554 openmano_cli.vnf_create(args.file.read())
555
556 elif args.command == "vnf-delete":
557 openmano_cli.vnf_delete(args.uuid)
558
559 elif args.command == "vnf-list":
560 for uuid, name in openmano_cli.vnf_list().items():
561 print("{} {}".format(uuid, name))
562
563 elif args.command == "scenario-create":
564 openmano_cli.ns_create(args.file.read())
565
566 elif args.command == "scenario-delete":
567 openmano_cli.ns_delete(args.uuid)
568
569 elif args.command == "scenario-list":
570 for uuid, name in openmano_cli.ns_list().items():
571 print("{} {}".format(uuid, name))
572
573 elif args.command == "scenario-deploy":
574 openmano_cli.ns_instantiate(args.scenario_name, args.instance_name)
575
576 elif args.command == "instance-scenario-delete":
577 openmano_cli.ns_terminate(args.instance_name)
578
579 elif args.command == "instance-scenario-list":
580 for uuid, name in openmano_cli.ns_instance_list().items():
581 print("{} {}".format(uuid, name))
582
583 elif args.command == "datacenter-list":
584 for uuid, name in openmano_cli.datacenter_list():
585 print("{} {}".format(uuid, name))
586
587 else:
588 logger.error("Unknown command: %s", args.command)
589 sys.exit(1)
590
591 if __name__ == "__main__":
592 main()