blob: 58d6ff05fb7d32dc56855f288d9671e46558bbe6 [file] [log] [blame]
tiernof7aa8c42016-09-06 16:43:04 +02001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# PYTHON_ARGCOMPLETE_OK
4
5##
6# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
tierno9a61c6b2016-09-08 10:57:02 +02007# This file is part of openvim
tiernof7aa8c42016-09-06 16:43:04 +02008# All Rights Reserved.
9#
10# Licensed under the Apache License, Version 2.0 (the "License"); you may
11# not use this file except in compliance with the License. You may obtain
12# a copy of the License at
13#
14# http://www.apache.org/licenses/LICENSE-2.0
15#
16# Unless required by applicable law or agreed to in writing, software
17# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19# License for the specific language governing permissions and limitations
20# under the License.
21#
22# For those usages not covered by the Apache License, Version 2.0 please
23# contact with: nfvlabs@tid.es
24##
25
26'''
27This is a client for managing the openvim server.
28Useful for human CLI management of items at openvim
29'''
30__author__="Alfonso Tierno, Gerardo Garcia"
31__date__ ="$26-nov-2014 19:09:29$"
32__version__="0.4.0-r443"
33version_date="Nov 2015"
34name="openvim"
35
36from argcomplete.completers import FilesCompleter
37import os
38import argparse
39import argcomplete
40import requests
41import json
42import yaml
43from jsonschema import validate as js_v, exceptions as js_e
44
45class ArgumentParserError(Exception): pass
46
47class ThrowingArgumentParser(argparse.ArgumentParser):
48 def error(self, message):
49 print "Error: %s" %message
50 print
51 self.print_usage()
52 #self.print_help()
53 print
54 print "Type 'openvim -h' for help"
55 raise ArgumentParserError
56
57global vim_config
58config_items=("HOST", "PORT", "ADMIN_PORT", "TENANT")
59show_at_verbose1=('status', 'created', 'path', 'last_error','description','hostId','progress',
60 'ram','vcpus','type','shared','admin_state_up', 'enabled', 'ip_name')
61
62#template for creating new items
63template={
64 "port":{
65 "${}":[
66 "${name} provide a port name",
67 "${net_id} provide the network uuid (null):",
68 "${vlan} provide the vlan if any (null):",
69 "${port} provide the attached switch port (Te0/47)",
70 "${mac} provide the mac of external device if known (null):"
71 ],
72 "port":{
73 "name": "${name}",
74 "network_id": "${net_id null}",
75 "type": "external",
76 "binding:vlan": "${vlan null-int}",
77 "binding:switch_port": "${port}",
78 "mac_address": "${mac null}"
79 },
80 },
81 "network":{
82 "${}":[
83 "${name} provide a network name",
84 "${type} provide a type: bridge_data,bridge_man,data,ptp (data)",
85 "${shared} external network: true,false (true)",
86 "${phy} conected to: bridge:name,macvtap:iface,default (null)"
87 ],
88 "network":{
89 "name": "${name}",
90 "type": "${type}",
91 "provider:physical": "${phy null}",
92 "shared": "${shared bool}"
93 }
94 },
95 "host": {
96 "${}":[
97 "${name} host name",
98 "${user} host user (user)",
99 "${ip_name} host access IP or name (${name})",
100 "${description} host description (${name})"
101 ],
102
103 "host":{
104 "name": "${name}",
105 "user": "${user}",
106 "ip_name": "${ip_name}",
107 "description": "${description}"
108 }
109 },
110 "flavor":{
111 "${}":[
112 "${name} flavor name",
113 "${description} flavor description (${name})",
114 "${processor_ranking} processor ranking (100)",
115 "${memory} memory in GB (2)",
116 "${threads} threads needed (2)"
117 ],
118 "flavor":{
119 "name":"${name}",
120 "description":"${description}",
121 "extended":{
122 "processor_ranking":"${processor_ranking int}",
123 "numas":[
124 {
125 "memory":"${memory int}",
126 "threads":"${threads int}"
127 }
128 ]
129 }
130 }
131 },
132 "tenant":{
133 "${}":[
134 "${name} tenant name",
135 "${description} tenant description (${name})"
136 ],
137 "tenant":{
138 "name": "${name}",
139 "description": "${description}"
140 }
141 },
142 "image":{
143 "${}":[
144 "${name} image name",
145 "${path} image path (/path/to/shared/compute/host/folder/image.qcow2)",
146 "${description} image description (${name})"
147 ],
148 "image":{
149 "name":"${name}",
150 "description":"${description}",
151 "path":"${path}"
152 }
153 },
154 "server":{
155 "${}":[
156 "${name} provide a name (VM)",
157 "${description} provide a description (${name})",
158 "${image_id} provide image_id uuid",
159 "${flavor_id} provide flavor_id uuid",
160 "${network0} provide a bridge network id; enter='default' (00000000-0000-0000-0000-000000000000)",
161 "${network1} provide a bridge network id; enter='virbrMan' (60f5227e-195f-11e4-836d-52540030594e)"
162 ],
163 "server":{
164 "networks":[
165 {
166 "name":"mgmt0",
167 "vpci": "0000:00:0a.0",
168 "uuid":"${network0}"
169 },
170 {
171 "name":"ge0",
172 "vpci": "0000:00:0b.0",
173 "uuid":"${network1}"
174 }
175 ],
176 "name":"${name}",
177 "description":"${description}",
178 "imageRef": "${image_id}",
179 "flavorRef": "${flavor_id}"
180 }
181 }
182}
183
184def check_configuration(*exception ):
185 '''
186 Check that the configuration variables are present.
187 exception can contain variables that are not tested, normally because is not used for the command
188 '''
189 #print exception
190 for item in config_items:
191 if item not in exception and vim_config[item]==None:
192 print "OPENVIM_"+item+" variable not defined. Try '" + name + " config file_cfg' or 'export OPENVIM_"+item+"=something'"
193 exit(-401); #HTTP_Unauthorized
194
195def check_valid_uuid(uuid):
196 '''
197 Determines if the param uuid is a well formed uuid. Otherwise it is consider a name
198 '''
199 id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
200 try:
201 js_v(uuid, id_schema)
202 return True
203 except js_e.ValidationError:
204 return False
205
206def _print_verbose(row_data, element, verbose=0):
207 #print row_data
208 data = row_data[element]
209 if verbose==1 or verbose==2:
210 data2= dict((k,v) for (k,v) in data.iteritems() if k in show_at_verbose1 and v!=None)
211 if 'image' in data and 'id' in data['image']:
212 data2['image'] = data['image']['id']
213 if 'flavor' in data and 'id' in data['flavor']:
214 data2['flavor'] = data['flavor']['id']
215 if verbose==2:
216 #TODO add more intems in a digest mode, extended, numas ...
217 pass
218 #if "numas" in data
219 #if "extended" in data
220 else:
221 data2= data
222 #print json.dumps(c2, indent=4)
223# keys = data.keys()
224# for key in keys:
225# if data[key]==None: del data[key]
226# elif key=='id' or key=='name' or key=='links': del data[key]
227
228 #print json.dumps(data2, indent=4)
229 data2={element: data2}
230 print yaml.safe_dump(data2, indent=4, default_flow_style=False)
231
232def vim_read(url):
233 '''
234 Send a GET http to VIM
235 '''
236 headers_req = {'content-type': 'application/json'}
237 try:
238 vim_response = requests.get(url, headers = headers_req)
239 if vim_response.status_code == 200:
240 #print vim_response.json()
241 #print json.dumps(vim_response.json(), indent=4)
242 content = vim_response.json()
243 return 1, content
244 #print http_content
245 else:
246 #print " Error. VIM response '%s': not possible to GET %s, error %s" % (vim_response.status_code, url, vim_response.text)
247 return -vim_response.status_code, vim_response.text
248 except requests.exceptions.RequestException, e:
249 return -1, " Exception GET at '"+url+"' " + str(e.message)
250
251def vim_delete(url):
252 '''
253 Send a DELETE http to VIM
254 '''
255 headers_req = {'content-type': 'application/json'}
256 try:
257 vim_response = requests.delete(url, headers = headers_req)
258 if vim_response.status_code != 200:
259 #print " Error. VIM response '%s': not possible to DELETE %s, error %s" % (vim_response.status_code, url, vim_response.text)
260 return -vim_response.status_code, vim_response.text
261 except requests.exceptions.RequestException, e:
262 return -1, " Exception DELETE at '"+url+"' " + str(e.message)
263 return 1, vim_response.json()
264
265def vim_action(url, payload):
266 '''
267 Send a POST http to VIM
268 '''
269 headers_req = {'content-type': 'application/json'}
270 try:
271 vim_response = requests.post(url, data=json.dumps(payload), headers = headers_req)
272 if vim_response.status_code != 200:
273 #print " Error. VIM response '%s': not possible to POST %s, error %s" % (vim_response.status_code, url, vim_response.text)
274 return -vim_response.status_code, vim_response.text
275 except requests.exceptions.RequestException, e:
276 return -1, " Exception POST at '"+url+"' " + str(e.message)
277 return 1, vim_response.json()
278
279def vim_edit(url, payload):
280 headers_req = {'content-type': 'application/json'}
281 try:
282 vim_response = requests.put(url, data=json.dumps(payload), headers = headers_req)
283 if vim_response.status_code != 200:
284 #print " Error. VIM response '%s': not possible to PUT %s, error %s" % (vim_response.status_code, url, vim_response.text)
285 return -vim_response.status_code, vim_response.text
286 except requests.exceptions.RequestException, e:
287 return -1, " Exception PUT at '"+url+"' " + str(e.message)
288 return 1, vim_response.json()
289
290def vim_create(url, payload):
291 headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
292 #print str(payload)
293 try:
294 vim_response = requests.post(url, data=json.dumps(payload), headers=headers_req)
295 if vim_response.status_code != 200:
296 #print " Error. VIM response '%s': not possible to POST %s, error %s" % (vim_response.status_code, url, vim_response.text)
297 return -vim_response.status_code, vim_response.text
298 except requests.exceptions.RequestException, e:
299 return -1, " Exception POST at '"+url+"' " + str(e.message)
300 return 1, vim_response.json()
301
302def parse_yaml_json(text, file_name=None):
303 parser_json=None
304 if file_name:
305 if file_name[-5:]=='.yaml' or file_name[-4:]=='.yml':
306 parser_json = False
307 elif file_name[-5:]=='.json':
308 parser_json = True
309 if parser_json==None:
310 parser_json=True if '\t' in text else False
311
312 if parser_json: #try parse in json format, because yaml does not admit tabs
313 try:
314 data = json.loads(text)
315 return 0, data
316 except Exception as e:
317 return -1, "Error json format: " + str(e)
318 #try to parse in yaml format
319 try:
320 data = yaml.load(text)
321 return 0, data
322 except yaml.YAMLError as exc:
323 error_pos = ""
324 if hasattr(exc, 'problem_mark'):
325 mark = exc.problem_mark
326 error_pos = " at line:%s column:%s" % (mark.line+1, mark.column+1)
327 return -1, " Error yaml format error" + error_pos
328
329def change_string(text, var_list):
330 '''
331 See change_var_recursively help
332 Used for changed any '${var format}' content inside the 'text' string
333 into the corresponding value at 'var_list'[var]
334 'format' is optional, string by default. Contain a - separated formats,ej, int-null
335 '''
336 end=0
337 type_=None
338 while True:
339 ini = text.find("${", end)
340 if ini<0: return text
341 end = text.find("}", ini)
342 if end<0: return text
343 end+=1
344
345 var = text[ini:end]
346 if ' ' in var:
347 kk=var.split(" ")
348 var=kk[0]+"}"
349 type_=kk[-1][:-1]
350 var = var_list.get(var, None)
351 if var==None: return text
352
353 text = text[:ini] + var + text[end:]
354 if type_ != None:
355 if 'null' in type_ and text=="null":
356 return None
357 if 'int' in type_ : #and text.isnumeric():
358 return int(text)
359 if 'bool' in type_ : #and text.isnumeric():
360 if text.lower()=="true": return True
361 elif text.lower()=="false": return False
362 else:
363 print "input boolean paramter must be 'true' or 'false'"
364 exit(1)
365 return text
366
367def chage_var_recursively(data, var_list):
368 '''
369 Check recursively the content of 'data', and look for "*${*}*" variables and changes
370 It assumes that this variables are not in the key of dictionary,
371 The overall target is having json/yaml templates with this variables in the text.
372 The user is asked for a value that is replaced wherever appears
373 Attributes:
374 'data': dictionary, or list. None or empty is considered valid
375 'var_list': dictionary (name:change) pairs
376 Return:
377 None, data is modified
378 '''
379
380 if type(data) is dict:
381 for k in data.keys():
382 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
383 chage_var_recursively(data[k], var_list)
384 elif type(data[k]) is str:
385 data[k] = change_string(data[k], var_list)
386 if type(data) is list:
387 for k in range(0,len(data)):
388 if type(data[k]) is dict or type(data[k]) is list:
389 chage_var_recursively(data[k], var_list)
390 elif type(data[k]) is str:
391 data[k] = change_string(data[k], var_list)
392
393def change_var(data, default_values={}):
394 ''' Look for a text "${}" key at 'data' that indicates that this json contains
395 variables that must be ask for values to the user and changes in the text of
396 the dictionary
397 'default_values' contain a dictionary of values that must be used and not be asked
398 Useful for creating templates
399 "${}" entry contain a list with the text:
400 ${var-name} prompt text to show user (Default value to allocate if user type CR)
401 '''
402 if type(data) is not dict:
403 return -1, "Format error, not a object (dictionary)"
404 if "${}" not in data:
405 return 0, data
406
407 var_list={}
408 for var in data["${}"]:
409 r = var.find("}",) + 1
410 if r<=2 or var[:2] != '${':
411 return -1, "Format error at '${}':" + var
412 #change variables inside description text
413 if "${" in var[r:]:
414 var = var[:r] + change_string(var[r:], var_list)
415 d_start = var.rfind("(",) + 1
416 d_end = var.rfind(")",)
417 if d_start>0 and d_end>=d_start:
418 default = var[d_start:d_end]
419 else: default=None
420 if var[2:r-1] in default_values:
421 var_list[ var[:r] ] = default_values[ var[2:r-1] ]
422 continue
423 v = raw_input(var[r:] + "? ")
424 if v=="":
425 if default != None:
426 v = default
427 else:
428 v = raw_input(" empty string? try again: ")
429 var_list[ var[:r] ] = str(v)
430 del data["${}"]
431 chage_var_recursively(data, var_list)
432 return 0, data
433
434def load_file(file_, parse=False):
435 try:
436 f = open(file_, 'r')
437 read_data = f.read()
438 f.close()
439 if not parse:
440 return 0, read_data
441 except IOError, e:
442 return -1, " Error opening file '" + file_ + "': " + e.args[1]
443 return parse_yaml_json(read_data, file_)
444
445def load_file_or_yaml(content):
446 '''
447 'content' can be or a yaml/json file or a text containing a yaml/json text format
448 This function autodetect, trying to load and parse the filename 'content',
449 if fails trying to parse the 'content' as text
450 Returns the dictionary once parsed, or print an error and finish the program
451 '''
452 r,payload = load_file(content, parse=True)
453 if r<0:
454 if r==-1 and "{" in content or ":" in content:
455 #try to parse directly
456 r,payload = parse_yaml_json(content)
457 if r<0:
458 print payload
459 exit (-1)
460 else:
461 print payload
462 exit (-1)
463 return payload
464
465def config(args):
466 #print "config-list",args
467 if args.file != None:
468 try:
469 f = open(args.file, 'r')
470 read_data = f.read()
471 f.close()
472 except IOError, e:
473 print " Error opening file '" + args.file + "': " + e.args[1]
474 return -1
475 try:
476 data = yaml.load(read_data)
477 #print data
478 if "http_host" in data:
479 print " export OPENVIM_HOST="+ data["http_host"]
480 if "http_port" in data:
481 print " export OPENVIM_PORT="+ str(data["http_port"])
482 #vim_config[item] = data["OPENVIM_"+item] #TODO find the way to change envioronment
483 #os.setenv('OPENVIM_'+item, vim_config[item])
484 if "http_admin_port" in data:
485 print " export OPENVIM_ADMIN_PORT="+ str(data["http_admin_port"])
486 if "tenant_id" in data:
487 print " export OPENVIM_TENANT="+ data["tenant_id"]
488 return 0
489 except yaml.YAMLError, exc:
490 error_pos = ""
491 if hasattr(exc, 'problem_mark'):
492 mark = exc.problem_mark
493 error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
494 print " Error yaml/json format error at '"+ args.file +"'"+error_pos
495 return -1
496 print args.file
497 print "OPENVIM_HOST: %s" %vim_config["HOST"]
498 print "OPENVIM_PORT: %s" %vim_config["PORT"]
499 print "OPENVIM_ADMIN_PORT: %s" %vim_config["ADMIN_PORT"]
500 print "OPENVIM_TENANT: %s" %vim_config["TENANT"]
501 return 0
502
503def element_new(args):
504 #print args
505 tenant=""
506 if args.element in ('flavors','images','servers'):
507 check_configuration( "ADMIN_PORT" )
508 tenant="/"+vim_config["TENANT"]
509 else:
510 check_configuration("ADMIN_PORT", "TENANT")
511 tenant=""
512
513 default_values={}
514 if args.name != None:
515 default_values["name"] = args.name
516 if "description" in args and args.description != None:
517 default_values["description"] = args.description
518 if "path" in args and args.path != None:
519 default_values["path"] = args.path
520 if args.file==None:
521 payload= template[args.element[:-1] ]
522 payload = yaml.load(str(payload)) #with this trick we make a completely copy of the data, so to not modified the original one
523 else:
524 payload=load_file_or_yaml(args.file)
525 r,c= change_var(payload, default_values)
526 if r<0:
527 print "Template error", c
528 return -1
529 payload=c
530 #print payload
531 if args.element[:-1] not in payload:
532 payload = {args.element[:-1]: payload }
533 item = payload[args.element[:-1]]
534
535 url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
536 if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
537 url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
538 else:
539 url_admin = None
540
541 if args.name != None:
542 item["name"] = args.name
543 if "description" in args and args.description != None:
544 item["description"] = args.description
545 if "path" in args and args.path != None:
546 item["path"] = args.path
547
548
549 r,c = vim_create(url, payload)
550 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
551 url=url_admin
552 r,c = vim_create(url, payload)
553 if r<0:
554 print c
555 return -r
556 else:
557 #print c
558 item=c[ args.element[:-1] ]
559 uuid=item.get('id', None)
560 if uuid is None:
561 uuid=item.get('uuid', 'uuid-not-found?')
562 name = item.get('name', '')
563 print " " + uuid +" "+ name.ljust(20) + " Created"
564 for e in ('warning', 'error', 'last_error'):
565 if e in item: print e + "\n" + item[e]
566 if args.verbose!=None:
567 _print_verbose(c, args.element[:-1], args.verbose)
568 return 0
569
570def element_action(args):
571 filter_qs = ""
572 tenant=""
573 if args.element in ('flavors','images','servers'):
574 check_configuration( "ADMIN_PORT")
575 tenant="/"+vim_config["TENANT"]
576 else:
577 check_configuration("ADMIN_PORT", "TENANT")
578 tenant=""
579 url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
580 if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
581 url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
582 else:
583 url_admin = None
584
585 if args.filter:
586 filter_qs += "?" + args.filter
587 if args.name!=None:
588 if check_valid_uuid(args.name):
589 if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
590 else: filter_qs += "?" + "id=" + str(args.name)
591 else:
592 if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
593 else: filter_qs += "?" + "name=" + str(args.name)
594
595
596 r,c = vim_read(url + filter_qs)
597 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
598 r,c = vim_read(url_admin + filter_qs)
599 if r<0:
600 print "Error:", c
601 return -r
602 if args.action=='createImage':
603 payload={ args.action: {"name":args.imageName} }
604 if args.description != None:
605 payload[args.action]["description"]=args.description
606 if args.path != None:
607 payload[args.action]["path"]=args.path
608 else:
609 payload={ args.action: None}
610 #print json.dumps(c, indent=4)
611 item_list = c[ args.element ]
612 if len(item_list)==0 and args.name != None:
613 print " Not found " + args.element + " " + args.name
614 return 404 #HTTP_Not_Found
615 result = 0
616 for item in item_list:
617 uuid=item.get('id', None)
618 if uuid is None:
619 uuid=item.get('uuid', None)
620 if uuid is None:
621 print "Id not found"
622 continue
623 name = item.get('name', '')
624 if not args.force:
625 r = raw_input(" Action over " + args.element + " " + uuid + " " + name + " (y/N)? ")
626 if len(r)>0 and r[0].lower()=="y":
627 pass
628 else:
629 continue
630 r,c = vim_action(url + "/" + uuid + "/action", payload)
631 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
632 url=url_admin
633 r,c = vim_action(url + "/" + uuid + "/action", payload)
634 if r<0:
635 print " " + uuid +" "+ name.ljust(20) + " " + c
636 result = -r
637 else:
638 if args.action == "createImage": #response contain an {image: {...} }, not a {server: {...} }.
639 print " " + c["image"]["id"] +" "+ c["image"]["name"].ljust(20)
640 args.element="images"
641 else:
642 print " " + uuid +" "+ name.ljust(20) + " "+ args.action
643 if "verbose" in args and args.verbose!=None:
644 _print_verbose(c, args.element[:-1], args.verbose)
645 return result
646
647def element_edit(args):
648 filter_qs = ""
649 tenant=""
650 if args.element in ('flavors','images','servers'):
651 check_configuration( "ADMIN_PORT")
652 tenant="/"+vim_config["TENANT"]
653 else:
654 check_configuration("ADMIN_PORT", "TENANT")
655 tenant=""
656
657 url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
658 if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
659 url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
660 else:
661 url_admin = None
662
663 if args.filter:
664 filter_qs += "?" + args.filter
665 if args.name!=None:
666 if check_valid_uuid(args.name):
667 if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
668 else: filter_qs += "?" + "id=" + str(args.name)
669 else:
670 if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
671 else: filter_qs += "?" + "name=" + str(args.name)
672
673
674 r,c = vim_read(url + filter_qs)
675 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
676 r,c = vim_read(url_admin + filter_qs)
677 if r<0:
678 print "Error:", c
679 return -r
680
681 payload=load_file_or_yaml(args.file)
682 r2,c2= change_var(payload)
683 if r2<0:
684 print "Template error", c2
685 return -1
686 payload=c2
687 if args.element[:-1] not in payload:
688 payload = {args.element[:-1]: payload }
689
690 #print json.dumps(c, indent=4)
691 item_list = c[ args.element ]
692 if len(item_list)==0 and args.name != None:
693 print " Not found " + args.element + " " + args.name
694 return 404 #HTTP_Not_Found
695 result = 0
696 for item in item_list:
697 uuid=item.get('id', None)
698 if uuid is None:
699 uuid=item.get('uuid', None)
700 if uuid is None:
701 print "Id not found"
702 continue
703 name = item.get('name', '')
704 if not args.force or (args.name==None and args.filer==None):
705 r = raw_input(" Edit " + args.element + " " + uuid + " " + name + " (y/N)? ")
706 if len(r)>0 and r[0].lower()=="y":
707 pass
708 else:
709 continue
710 r,c = vim_edit(url + "/" + uuid, payload)
711 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
712 url=url_admin
713 r,c = vim_edit(url + "/" + uuid, payload)
714 if r<0:
715 print " " + uuid +" "+ name.ljust(20) + " " + c
716 result = -r
717 else:
718 print " " + uuid +" "+ name.ljust(20) + " edited"
719 if "verbose" in args and args.verbose!=None:
720 _print_verbose(c, args.element[:-1], args.verbose)
721 return result
722
723def element_action_edit(args):
724 #print args
725 if args.element=='ports':
726 if args.action=='attach':
727 args.file='network_id: ' + args.network_id
728 else: #args.action=='detach'
729 args.file='network_id: null'
730 if args.action=='up':
731 args.file='admin_state_up: true'
732 if args.action=='down':
733 args.file='admin_state_up: false'
734 return element_edit(args)
735
736def element_delete(args):
737 filter_qs = ""
738 tenant=""
739 if args.element in ('flavors','images','servers'):
740 check_configuration("ADMIN_PORT" )
741 tenant="/"+vim_config["TENANT"]
742 else:
743 check_configuration("ADMIN_PORT", "TENANT")
744 tenant=""
745
746 url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
747 if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
748 url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
749 else:
750 url_admin = None
751
752 if args.filter:
753 filter_qs += "?" + args.filter
754 if args.name!=None:
755 if check_valid_uuid(args.name):
756 if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
757 else: filter_qs += "?" + "id=" + str(args.name)
758 else:
759 if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
760 else: filter_qs += "?" + "name=" + str(args.name)
761
762
763 r,c = vim_read(url + filter_qs)
764 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
765 r,c = vim_read(url_admin + filter_qs)
766 if r<0:
767 print "Error:", c
768 return -r
769
770 #print json.dumps(c, indent=4)
771 item_list = c[ args.element ]
772 if len(item_list)==0 and args.name != None:
773 print " Not found " + args.element + " " + args.name
774 return 404 #HTTP_Not_Found
775 result = 0
776 for item in item_list:
777 uuid=item.get('id', None)
778 if uuid is None:
779 uuid=item.get('uuid', None)
780 if uuid is None:
781 print "Id not found"
782 result = -500
783 continue
784 name = item.get('name', '')
785 if not args.force:
786 r = raw_input(" Delete " + args.element + " " + uuid + " " + name + " (y/N)? ")
787 if len(r)>0 and r[0].lower()=="y":
788 pass
789 else:
790 continue
791 r,c = vim_delete(url + "/" + uuid)
792 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
793 url=url_admin
794 r,c = vim_delete(url + "/" + uuid)
795 if r<0:
796 print " " + uuid +" "+ name.ljust(20) + " " + c
797 result = -r
798 else:
799 print " " + uuid +" "+ name.ljust(20) + " deleted"
800 return result
801
802def element_list(args):
803 #print "element_list", args
804 filter_qs = ""
805 tenant=""
806 if args.element in ('flavors','images','servers'):
807 check_configuration( "ADMIN_PORT" )
808 tenant="/"+vim_config["TENANT"]
809 else:
810 check_configuration( "ADMIN_PORT", "TENANT" )
811 tenant=""
812 #if args.name:
813 # what += "/" + args.name
814 url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
815 if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
816 url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
817 else:
818 url_admin = None
819 #print " get", what, " >>>>>>>> ",
820 if args.filter:
821 filter_qs += "?" + args.filter
822 if args.name!=None:
823 if check_valid_uuid(args.name):
824 if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
825 else: filter_qs += "?" + "id=" + str(args.name)
826 else:
827 if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
828 else: filter_qs += "?" + "name=" + str(args.name)
829
830
831 r,c = vim_read(url + filter_qs)
832 if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
833 r,c = vim_read(url_admin + filter_qs)
834 if r<0:
835 print "Error:", c
836 return -r
837 #print json.dumps(c, indent=4)
838 result = 0
839 item_list = c[ args.element ]
840 verbose=0
841 #if args.name!=None and len(item_list)==1:
842 # verbose+=1
843 if args.verbose!=None:
844 verbose += args.verbose
845 for item in item_list:
846 extra=""
847 if args.element=="servers" or args.element=="networks": extra = " "+item['status']
848 if args.element in ("hosts","networks","ports") and not item['admin_state_up']: extra += " admin_state_up=false"
849 print item['id']+" "+item['name'].ljust(20) + extra
850 if verbose>0:
851 r2,c2 = vim_read(url + "/"+ item['id'])
852 if r2==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
853 url=url_admin
854 r2,c2 = vim_read(url + "/"+ item['id'])
855 if r2>0:
856 _print_verbose(c2, args.element[:-1], verbose)
857
858 return result
859
860def openflow_action(args):
861 if args.action=='port-list':
862 url = "http://%s:%s/openvim/networks/openflow/ports" %(vim_config["HOST"], vim_config["PORT"])
863 r,c = vim_read(url)
864 elif args.action=='rules-list' or args.action=='reinstall':
865 PORT = vim_config["PORT"]
866 if args.action=='reinstall':
867 if "ADMIN_PORT" not in vim_config:
868 print "OPENVIM_ADMIN_PORT variable not defined"
869 return 404 #HTTP_Not_Found
870 PORT = vim_config["ADMIN_PORT"]
871 if args.name!=None:
872 url = "http://%s:%s/openvim/networks" %(vim_config["HOST"], PORT)
873 if check_valid_uuid(args.name):
874 url += "?id=" + str(args.name)
875 else:
876 url += "?name=" + str(args.name)
877 r,c = vim_read(url)
878 if r<0:
879 print "Error:", c
880 return -r
881 if len (c["networks"]) == 0:
882 print " Network not found"
883 return 404 #HTTP_Not_Found
884 if len (c["networks"]) > 1:
885 print " More than one net with this name found. Use uuid instead of name to concretize"
886 return 404 #HTTP_Not_Found
887 network = c["networks"][0]["id"]
888 else:
889 network="all"
890 url = "http://%s:%s/openvim/networks/%s/openflow" %(vim_config["HOST"], PORT, network)
891 if args.action=='reinstall':
892 r,c = vim_edit(url, None)
893 else:
894 r,c = vim_read(url)
895 elif args.action=='clear-all':
896 if "ADMIN_PORT" not in vim_config:
897 print "OPENVIM_ADMIN_PORT variable not defined"
898 return 401 # HTTP_Unauthorized
mirabal6c600652017-03-16 17:22:57 +0100899 url = "http://%s:%s/openvim/networks/clear/openflow" %(vim_config["HOST"], vim_config["ADMIN_PORT"])
tiernof7aa8c42016-09-06 16:43:04 +0200900 r,c = vim_delete(url)
901 else:
902 return 400 #HTTP_Bad_Request
903 if r<0:
904 print "Error:", c
905 return -r
906 else:
907 print yaml.safe_dump(c, indent=4, default_flow_style=False)
908 return 0
909
910if __name__=="__main__":
911
912 item_dict={'vm':'servers','host':'hosts','tenant':'tenants','image':'images','flavor':'flavors','net':'networks','port':'ports'}
913
914 vim_config={}
915 vim_config["HOST"] = os.getenv('OPENVIM_HOST', 'localhost')
916 vim_config["PORT"] = os.getenv('OPENVIM_PORT', '9080')
917 vim_config["ADMIN_PORT"] = os.getenv('OPENVIM_ADMIN_PORT', '9085')
918 vim_config["TENANT"] = os.getenv('OPENVIM_TENANT', None)
919
920
921 main_parser = ThrowingArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)')
922 #main_parser = argparse.ArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)')
923 main_parser.add_argument('--version', action='version', version='%(prog)s ' + __version__ + ' '+version_date)
924
925 subparsers = main_parser.add_subparsers(help='commands')
926
927 config_parser = subparsers.add_parser('config', help="prints configuration values")
928 config_parser.add_argument("file", nargs='?', help="configuration file to extract the configuration").completer = FilesCompleter
929 config_parser.set_defaults(func=config)
930 #HELP
931 for item in item_dict:
932 #LIST
933 element_list_parser = subparsers.add_parser(item+'-list', help="lists information about "+item_dict[item])
934 element_list_parser.add_argument("name", nargs='?', help="name or ID of the " + item)
935 element_list_parser.add_argument("-F","--filter", action="store", help="filter query string")
936 element_list_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
937 element_list_parser.set_defaults(func=element_list, element=item_dict[item])
938 #NEW
939 if item=='host':
940 element_new_parser = subparsers.add_parser(item+'-add', help="adds a new compute node")
941 else:
942 element_new_parser = subparsers.add_parser(item+'-create', help="creates a new "+item_dict[item][:-1])
943 element_new_parser.add_argument("file", nargs='?', help="json/yaml text or file with content").completer = FilesCompleter
944 element_new_parser.add_argument("--name", action="store", help="Use this name")
945 if item!="network":
946 element_new_parser.add_argument("--description", action="store", help="Use this descrition")
947 if item=="image":
948 element_new_parser.add_argument("--path", action="store", help="Use this path")
949 element_new_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
950 element_new_parser.set_defaults(func=element_new, element=item_dict[item])
951 #DELETE
952 if item=='host':
953 element_del_parser = subparsers.add_parser(item+'-remove', help="removes a compute node")
954 else:
955 element_del_parser = subparsers.add_parser(item+'-delete', help="deletes one or several "+item_dict[item])
956 element_del_parser.add_argument("name", nargs='?', help="name or ID of the "+item+", if missing means all")
957 element_del_parser.add_argument("-F","--filter", action="store", help="filter query string")
958 element_del_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
959 element_del_parser.set_defaults(func=element_delete, element=item_dict[item])
960 #EDIT
961 element_edit_parser = subparsers.add_parser(item+'-edit', help="edits one or several "+item_dict[item])
962 element_edit_parser.add_argument("name", help="name or ID of the "+item+"")
963 element_edit_parser.add_argument("file", help="json/yaml text or file with the changes").completer = FilesCompleter
964 element_edit_parser.add_argument("-F","--filter", action="store", help="filter query string")
965 element_edit_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
966 element_edit_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
967 element_edit_parser.set_defaults(func=element_edit, element=item_dict[item])
968 #ACTION
969 if item=='vm':
970 for item2 in ('shutdown', 'start', 'rebuild', 'reboot'):
971 vm_action_parser = subparsers.add_parser("vm-"+item2, help="performs this action over the virtual machine")
972 vm_action_parser.add_argument("name", nargs='?', help="name or ID of the server, if missing means all")
973 vm_action_parser.add_argument("-F","--filter", action="store", help="filter query string")
974 vm_action_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
975 vm_action_parser.set_defaults(func=element_action, element="servers", action=item2 )
976 vm_action_image_parser = subparsers.add_parser("vm-createImage", help="creates a snapshot of the virtual machine disk into a new image")
977 vm_action_image_parser.add_argument("name", help="name or ID of the server")
978 vm_action_image_parser.add_argument("imageName", help="image name")
979 vm_action_image_parser.add_argument("--description", action="store", help="Provide a new image description")
980 vm_action_image_parser.add_argument("--path", action="store", help="Provide a new image complete path")
981 vm_action_image_parser.add_argument("-F","--filter", action="store", help="filter query string")
982 vm_action_image_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
983 vm_action_image_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
984 vm_action_image_parser.set_defaults(func=element_action, element="servers", action="createImage" )
985 #ACTION that are implemented with EDITION
986 if item=='port':
987 port_action_attach_parser = subparsers.add_parser("port-attach", help="connects a port to a network")
988 port_action_attach_parser.add_argument("name", help="name or ID of the port")
989 port_action_attach_parser.add_argument("network_id", help="ID of the network")
990 port_action_attach_parser.add_argument("-F","--filter", action="store", help="filter query string")
991 port_action_attach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
992 port_action_attach_parser.set_defaults(func=element_action_edit, element="ports", action="attach")
993
994 port_action_detach_parser = subparsers.add_parser("port-detach", help="removes a port from a network")
995 port_action_detach_parser.add_argument("name", help="name or ID of the port")
996 port_action_detach_parser.add_argument("-F","--filter", action="store", help="filter query string")
997 port_action_detach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
998 port_action_detach_parser.set_defaults(func=element_action_edit, element="ports", action="dettach")
999
1000 if item=='net' or item=='host':
1001 nethost_action_up_parser = subparsers.add_parser(item+"-up", help="puts admin_state_up of "+item_dict[item][:-1]+" to true")
1002 nethost_action_up_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1])
1003 nethost_action_up_parser.add_argument("-F","--filter", action="store", help="filter query string")
1004 nethost_action_up_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
1005 nethost_action_up_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="up")
1006
1007 nethost_action_down_parser = subparsers.add_parser(item+"-down", help="puts admin_state_up of "+item_dict[item][:-1]+" to false")
1008 nethost_action_down_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1])
1009 nethost_action_down_parser.add_argument("-F","--filter", action="store", help="filter query string")
1010 nethost_action_down_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
1011 nethost_action_down_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="down")
1012
1013 #openflow rules
1014 openflow_list_action = subparsers.add_parser("openflow-port-list", help="list openflow switch ports name")
1015 openflow_list_action.set_defaults(func=openflow_action, action="port-list")
1016
1017 openflow_list_action = subparsers.add_parser("openflow-clear-all", help="removes all openflow rules")
1018 openflow_list_action.set_defaults(func=openflow_action, action="clear-all")
1019
1020 openflow_list_action = subparsers.add_parser("openflow-net-reinstall", help="reinstall the openflow rules for a network")
1021 openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks")
1022 openflow_list_action.set_defaults(func=openflow_action, action="reinstall")
1023
1024 openflow_list_action = subparsers.add_parser("openflow-net-list", help="list installed openflow rules for a network")
1025 openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks")
1026 openflow_list_action.set_defaults(func=openflow_action, action="rules-list")
1027
1028 argcomplete.autocomplete(main_parser)
1029
1030 try:
1031 args = main_parser.parse_args()
1032 result = args.func(args)
1033 if result == None:
1034 result = 0
1035 #for some reason it fails if call exit inside try instance. Need to call exit at the end !?
1036 except (requests.exceptions.ConnectionError):
1037 print "Connection error: not possible to contact OPENVIM-SERVER (openvimd)"
1038 result = -2
1039 except (KeyboardInterrupt):
1040 print 'Exiting openVIM'
1041 result = -3
1042 except (SystemExit, ArgumentParserError):
1043 result = -4
1044
1045 #print result
1046 exit(result)
1047