814a50c198dbb9b413872aac023c7181cddc2b69
[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 except OpenmanoCommandFailed as e:
202 self._log.warning("Vnf listing returned an error: %s", str(e))
203 return {}
204
205 name_uuid_map = {}
206 for line in output_lines:
207 line = line.strip()
208 uuid, name = line.split(" ", 1)
209 name_uuid_map[name] = uuid
210
211 return name_uuid_map
212
213 def ns_create(self, ns_yaml_str, name=None):
214 self._log.info("Creating NS: %s", ns_yaml_str)
215
216 with tempfile.NamedTemporaryFile() as ns_file_hdl:
217 ns_file_hdl.write(ns_yaml_str.encode())
218 ns_file_hdl.flush()
219
220 cmd_args = ["scenario-create", ns_file_hdl.name]
221 if name is not None:
222 cmd_args.extend(["--name", name])
223
224 output_lines = self._openmano_cmd(
225 cmd_args,
226 expected_lines=1
227 )
228
229 ns_info_line = output_lines[0]
230 ns_id, ns_name = ns_info_line.split(" ", 1)
231
232 self._log.info("NS %s Created: %s", ns_name, ns_id)
233
234 return ns_id, ns_name
235
236 def ns_list(self):
237 self._log.debug("Getting NS list")
238
239 try:
240 output_lines = self._openmano_cmd(
241 ["scenario-list"],
242 )
243
244 except OpenmanoCommandFailed as e:
245 self._log.warning("NS listing returned an error: %s", str(e))
246 return {}
247
248 name_uuid_map = {}
249 for line in output_lines:
250 line = line.strip()
251 uuid, name = line.split(" ", 1)
252 name_uuid_map[name] = uuid
253
254 return name_uuid_map
255
256 def ns_delete(self, ns_uuid):
257 self._log.info("Deleting NS: %s", ns_uuid)
258
259 self._openmano_cmd(
260 ["scenario-delete", ns_uuid, "-f"],
261 )
262
263 self._log.info("NS Deleted: %s", ns_uuid)
264
265 def ns_instance_list(self):
266 self._log.debug("Getting NS instance list")
267
268 try:
269 output_lines = self._openmano_cmd(
270 ["instance-scenario-list"],
271 )
272
273 except OpenmanoCommandFailed as e:
274 self._log.warning("Instance scenario listing returned an error: %s", str(e))
275 return {}
276
277 if "No scenario instances were found" in output_lines[0]:
278 self._log.debug("No openmano instances were found")
279 return {}
280
281 name_uuid_map = {}
282 for line in output_lines:
283 line = line.strip()
284 uuid, name = line.split(" ", 1)
285 name_uuid_map[name] = uuid
286
287 return name_uuid_map
288
289 def ns_instance_scenario_create(self, instance_yaml_str):
290 """ Create a Openmano NS instance from input YAML string """
291
292 self._log.debug("Instantiating instance: %s", instance_yaml_str)
293
294 with tempfile.NamedTemporaryFile() as ns_instance_file_hdl:
295 ns_instance_file_hdl.write(instance_yaml_str.encode())
296 ns_instance_file_hdl.flush()
297
298 try:
299 output_lines = self._openmano_cmd(
300 ["instance-scenario-create", ns_instance_file_hdl.name],
301 expected_lines=1
302 )
303 except OpenmanoCommandFailed as e:
304 raise
305
306 uuid, _ = output_lines[0].split(" ", 1)
307
308 self._log.info("NS Instance Created: %s", uuid)
309
310 return uuid
311
312
313 def ns_vim_network_create(self, net_create_yaml_str,datacenter_name):
314 """ Create a Openmano VIM network from input YAML string """
315
316 self._log.debug("Creating VIM network instance: %s, DC %s", net_create_yaml_str,datacenter_name)
317
318 with tempfile.NamedTemporaryFile() as net_create_file_hdl:
319 net_create_file_hdl.write(net_create_yaml_str.encode())
320 net_create_file_hdl.flush()
321
322 try:
323 output_lines = self._openmano_cmd(
324 ["vim-net-create","--datacenter", datacenter_name, net_create_file_hdl.name],
325 expected_lines=1
326 )
327 except OpenmanoCommandFailed as e:
328 raise
329
330 uuid, _ = output_lines[0].split(" ", 1)
331
332 self._log.info("VIM Networks created in DC %s with ID: %s", datacenter_name, uuid)
333
334 return uuid
335
336 def ns_vim_network_delete(self, network_name,datacenter_name):
337 """ Delete a Openmano VIM network with given name """
338
339 self._log.debug("Deleting VIM network instance: %s, DC %s", network_name,datacenter_name)
340 try:
341 output_lines = self._openmano_cmd(
342 ["vim-net-delete","--datacenter", datacenter_name, network_name],
343 expected_lines=1
344 )
345 except OpenmanoCommandFailed as e:
346 raise
347 self._log.info("VIM Network deleted in DC %s with name: %s", datacenter_name, network_name)
348
349
350 def ns_instantiate(self, scenario_name, instance_name, datacenter_name=None):
351 self._log.info(
352 "Instantiating NS %s using instance name %s",
353 scenario_name,
354 instance_name,
355 )
356
357 cmd_args = ["scenario-deploy", scenario_name, instance_name]
358 if datacenter_name is not None:
359 cmd_args.extend(["--datacenter", datacenter_name])
360
361 output_lines = self._openmano_cmd(
362 cmd_args,
363 expected_lines=4
364 )
365
366 uuid, _ = output_lines[0].split(" ", 1)
367
368 self._log.info("NS Instance Created: %s", uuid)
369
370 return uuid
371
372 def ns_terminate(self, ns_instance_name):
373 self._log.info("Terminating NS: %s", ns_instance_name)
374
375 self._openmano_cmd(
376 ["instance-scenario-delete", ns_instance_name, "-f"],
377 )
378
379 self._log.info("NS Instance Deleted: %s", ns_instance_name)
380
381 def datacenter_list(self):
382 lines = self._openmano_cmd(["datacenter-list",])
383
384 # The results returned from openmano are formatted with whitespace and
385 # datacenter names may contain whitespace as well, so we use a regular
386 # expression to parse each line of the results return from openmano to
387 # extract the uuid and name of a datacenter.
388 hex = '[0-9a-fA-F]'
389 uuid_pattern = '(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)'.replace('x', hex)
390 name_pattern = '(.+?)'
391 datacenter_regex = re.compile(r'{uuid}\s+\b{name}\s*$'.format(
392 uuid=uuid_pattern,
393 name=name_pattern,
394 ))
395
396 # Parse the results for the datacenter uuids and names
397 datacenters = list()
398 for line in lines:
399 result = datacenter_regex.match(line)
400 if result is not None:
401 uuid, name = result.groups()
402 datacenters.append((uuid, name))
403
404 return datacenters
405
406
407 def valid_uuid(uuid_str):
408 uuid_re = re.compile(
409 "^xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx$".replace('x', '[0-9a-fA-F]')
410 )
411
412 if not uuid_re.match(uuid_str):
413 raise argparse.ArgumentTypeError("Got a valid uuid: %s" % uuid_str)
414
415 return uuid_str
416
417
418 def parse_args(argv=sys.argv[1:]):
419 """ Parse the command line arguments
420
421 Arguments:
422 argv - The list of arguments to parse
423
424 Returns:
425 Argparse Namespace instance
426 """
427 parser = argparse.ArgumentParser()
428 parser.add_argument(
429 '-d', '--host',
430 default='localhost',
431 help="Openmano host/ip",
432 )
433
434 parser.add_argument(
435 '-p', '--port',
436 default='9090',
437 help="Openmano port",
438 )
439
440 parser.add_argument(
441 '-t', '--tenant',
442 required=True,
443 type=valid_uuid,
444 help="Openmano tenant uuid to use",
445 )
446
447 subparsers = parser.add_subparsers(dest='command', help='openmano commands')
448
449 vnf_create_parser = subparsers.add_parser(
450 'vnf-create',
451 help="Adds a openmano vnf into the catalog"
452 )
453 vnf_create_parser.add_argument(
454 "file",
455 help="location of the JSON file describing the VNF",
456 type=argparse.FileType('rb'),
457 )
458
459 vnf_delete_parser = subparsers.add_parser(
460 'vnf-delete',
461 help="Deletes a openmano vnf into the catalog"
462 )
463 vnf_delete_parser.add_argument(
464 "uuid",
465 help="The vnf to delete",
466 type=valid_uuid,
467 )
468
469
470 ns_create_parser = subparsers.add_parser(
471 'scenario-create',
472 help="Adds a openmano ns scenario into the catalog"
473 )
474 ns_create_parser.add_argument(
475 "file",
476 help="location of the JSON file describing the NS",
477 type=argparse.FileType('rb'),
478 )
479
480 ns_delete_parser = subparsers.add_parser(
481 'scenario-delete',
482 help="Deletes a openmano ns into the catalog"
483 )
484 ns_delete_parser.add_argument(
485 "uuid",
486 help="The ns to delete",
487 type=valid_uuid,
488 )
489
490
491 ns_instance_create_parser = subparsers.add_parser(
492 'scenario-deploy',
493 help="Deploys a openmano ns scenario into the catalog"
494 )
495 ns_instance_create_parser.add_argument(
496 "scenario_name",
497 help="The ns scenario name to deploy",
498 )
499 ns_instance_create_parser.add_argument(
500 "instance_name",
501 help="The ns instance name to deploy",
502 )
503
504
505 ns_instance_delete_parser = subparsers.add_parser(
506 'instance-scenario-delete',
507 help="Deploys a openmano ns scenario into the catalog"
508 )
509 ns_instance_delete_parser.add_argument(
510 "instance_name",
511 help="The ns instance name to delete",
512 )
513
514
515 _ = subparsers.add_parser(
516 'datacenter-list',
517 )
518
519 args = parser.parse_args(argv)
520
521 return args
522
523
524 def main():
525 logging.basicConfig(level=logging.DEBUG)
526 logger = logging.getLogger("openmano_client.py")
527
528 if "RIFT_INSTALL" not in os.environ:
529 logger.error("Must be in rift-shell to run.")
530 sys.exit(1)
531
532 args = parse_args()
533 openmano_cli = OpenmanoCliAPI(logger, args.host, args.port, args.tenant)
534
535 if args.command == "vnf-create":
536 openmano_cli.vnf_create(args.file.read())
537
538 elif args.command == "vnf-delete":
539 openmano_cli.vnf_delete(args.uuid)
540
541 elif args.command == "scenario-create":
542 openmano_cli.ns_create(args.file.read())
543
544 elif args.command == "scenario-delete":
545 openmano_cli.ns_delete(args.uuid)
546
547 elif args.command == "scenario-deploy":
548 openmano_cli.ns_instantiate(args.scenario_name, args.instance_name)
549
550 elif args.command == "instance-scenario-delete":
551 openmano_cli.ns_terminate(args.instance_name)
552
553 elif args.command == "datacenter-list":
554 for uuid, name in openmano_cli.datacenter_list():
555 print("{} {}".format(uuid, name))
556
557 else:
558 logger.error("Unknown command: %s", args.command)
559 sys.exit(1)
560
561 if __name__ == "__main__":
562 main()