Fixed some typos
[osm/openvim.git] / openvim
1 #!/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.
7 # This file is part of openvim
8 # 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 '''
27 This is a client for managing the openvim server.
28 Useful 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"
33 version_date="Nov 2015"
34 name="openvim"
35
36 from argcomplete.completers import FilesCompleter
37 import os
38 import argparse
39 import argcomplete
40 import requests
41 import json
42 import yaml
43 from jsonschema import validate as js_v, exceptions as js_e
44
45 class ArgumentParserError(Exception): pass
46
47 class 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
57 global vim_config
58 config_items=("HOST", "PORT", "ADMIN_PORT", "TENANT")
59 show_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
63 template={
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
184 def 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
195 def 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
206 def _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     
232 def 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
251 def 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
265 def 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
279 def 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
290 def 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
302 def 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
329 def 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
367 def 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
393 def 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
434 def 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
445 def 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
465 def 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
503 def 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
570 def 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
647 def 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
723 def 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     
736 def 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
802 def 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
860 def 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 == 'port-mapping':
865         url = "http://%s:%s/openvim/openflow/mapping" % (vim_config["HOST"], vim_config["PORT"])
866         r, c = vim_read(url)
867     elif args.action=='rules-list' or args.action=='reinstall':
868         PORT = vim_config["PORT"]
869         if args.action=='reinstall':
870             if "ADMIN_PORT" not in vim_config:
871                 print "OPENVIM_ADMIN_PORT variable not defined"
872                 return 404 #HTTP_Not_Found
873             PORT = vim_config["ADMIN_PORT"]
874         if args.name!=None:
875             url = "http://%s:%s/openvim/networks" %(vim_config["HOST"], PORT)
876             if check_valid_uuid(args.name):
877                 url += "?id=" + str(args.name)
878             else:
879                 url += "?name=" + str(args.name)
880             r,c = vim_read(url)
881             if r<0:           
882                 print "Error:", c
883                 return -r
884             if len (c["networks"]) == 0:
885                 print "  Network not found"
886                 return 404 #HTTP_Not_Found
887             if len (c["networks"]) > 1:
888                 print "  More than one net with this name found. Use uuid instead of name to concretize"
889                 return 404 #HTTP_Not_Found
890             network = c["networks"][0]["id"]
891         else:
892             network="all"
893         url = "http://%s:%s/openvim/networks/%s/openflow" %(vim_config["HOST"], PORT, network)
894         if args.action=='reinstall':
895             r,c = vim_edit(url, None)
896         else:
897             r,c = vim_read(url)
898     elif args.action=='clear-all':
899         if "ADMIN_PORT" not in vim_config:
900             print "OPENVIM_ADMIN_PORT variable not defined"
901             return 401 # HTTP_Unauthorized
902         url = "http://%s:%s/openvim/networks/clear/openflow" %(vim_config["HOST"], vim_config["ADMIN_PORT"])
903         r,c = vim_delete(url)
904     else:
905         return 400 #HTTP_Bad_Request
906     if r<0:           
907         print "Error:", c
908         return -r
909     else:
910         print yaml.safe_dump(c, indent=4, default_flow_style=False)
911         return 0
912
913 if __name__=="__main__":
914     
915     item_dict={'vm':'servers','host':'hosts','tenant':'tenants','image':'images','flavor':'flavors','net':'networks','port':'ports'}
916     
917     vim_config={}
918     vim_config["HOST"] =       os.getenv('OPENVIM_HOST', 'localhost')
919     vim_config["PORT"] =       os.getenv('OPENVIM_PORT', '9080')
920     vim_config["ADMIN_PORT"] = os.getenv('OPENVIM_ADMIN_PORT', '9085')
921     vim_config["TENANT"] =     os.getenv('OPENVIM_TENANT', None)
922     
923    
924     main_parser = ThrowingArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)')
925     #main_parser = argparse.ArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)')
926     main_parser.add_argument('--version', action='version', version='%(prog)s ' + __version__ + ' '+version_date)
927     
928     subparsers = main_parser.add_subparsers(help='commands')
929     
930     config_parser = subparsers.add_parser('config', help="prints configuration values")
931     config_parser.add_argument("file", nargs='?', help="configuration file to extract the configuration").completer = FilesCompleter
932     config_parser.set_defaults(func=config)
933     #HELP
934     for item in item_dict:
935         #LIST
936         element_list_parser = subparsers.add_parser(item+'-list', help="lists information about "+item_dict[item])
937         element_list_parser.add_argument("name", nargs='?', help="name or ID of the " + item)
938         element_list_parser.add_argument("-F","--filter", action="store", help="filter query string")
939         element_list_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
940         element_list_parser.set_defaults(func=element_list, element=item_dict[item])
941         #NEW
942         if item=='host':
943             element_new_parser = subparsers.add_parser(item+'-add', help="adds a new compute node")
944         else:
945             element_new_parser = subparsers.add_parser(item+'-create', help="creates a new "+item_dict[item][:-1])
946         element_new_parser.add_argument("file", nargs='?', help="json/yaml text or file with content").completer = FilesCompleter
947         element_new_parser.add_argument("--name", action="store", help="Use this name")
948         if item!="network":
949             element_new_parser.add_argument("--description", action="store", help="Use this descrition")
950         if item=="image":
951             element_new_parser.add_argument("--path", action="store", help="Use this path")
952         element_new_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
953         element_new_parser.set_defaults(func=element_new, element=item_dict[item])
954         #DELETE
955         if item=='host':
956             element_del_parser = subparsers.add_parser(item+'-remove', help="removes a compute node")
957         else:
958             element_del_parser = subparsers.add_parser(item+'-delete', help="deletes one or several "+item_dict[item])
959         element_del_parser.add_argument("name", nargs='?', help="name or ID of the "+item+", if missing means all")
960         element_del_parser.add_argument("-F","--filter", action="store", help="filter query string")
961         element_del_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
962         element_del_parser.set_defaults(func=element_delete, element=item_dict[item])
963         #EDIT
964         element_edit_parser = subparsers.add_parser(item+'-edit', help="edits one or several "+item_dict[item])
965         element_edit_parser.add_argument("name", help="name or ID of the "+item+"")
966         element_edit_parser.add_argument("file", help="json/yaml text or file with the changes").completer = FilesCompleter
967         element_edit_parser.add_argument("-F","--filter", action="store", help="filter query string")
968         element_edit_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
969         element_edit_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
970         element_edit_parser.set_defaults(func=element_edit, element=item_dict[item])
971         #ACTION
972         if item=='vm':
973             for item2 in ('shutdown', 'start', 'rebuild', 'reboot'):
974                 vm_action_parser = subparsers.add_parser("vm-"+item2, help="performs this action over the virtual machine")
975                 vm_action_parser.add_argument("name", nargs='?', help="name or ID of the server, if missing means all")
976                 vm_action_parser.add_argument("-F","--filter", action="store", help="filter query string")
977                 vm_action_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
978                 vm_action_parser.set_defaults(func=element_action, element="servers", action=item2 )
979             vm_action_image_parser = subparsers.add_parser("vm-createImage", help="creates a snapshot of the virtual machine disk into a new image")
980             vm_action_image_parser.add_argument("name", help="name or ID of the server")
981             vm_action_image_parser.add_argument("imageName", help="image name")
982             vm_action_image_parser.add_argument("--description", action="store", help="Provide a new image description")
983             vm_action_image_parser.add_argument("--path", action="store", help="Provide a new image complete path")
984             vm_action_image_parser.add_argument("-F","--filter", action="store", help="filter query string")
985             vm_action_image_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
986             vm_action_image_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
987             vm_action_image_parser.set_defaults(func=element_action, element="servers", action="createImage" )
988         #ACTION that are implemented with EDITION
989         if item=='port':
990             port_action_attach_parser = subparsers.add_parser("port-attach", help="connects a port to a network")
991             port_action_attach_parser.add_argument("name", help="name or ID of the port")
992             port_action_attach_parser.add_argument("network_id", help="ID of the network")
993             port_action_attach_parser.add_argument("-F","--filter", action="store", help="filter query string")
994             port_action_attach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
995             port_action_attach_parser.set_defaults(func=element_action_edit, element="ports", action="attach")
996
997             port_action_detach_parser = subparsers.add_parser("port-detach", help="removes a port from a network")
998             port_action_detach_parser.add_argument("name", help="name or ID of the port")
999             port_action_detach_parser.add_argument("-F","--filter", action="store", help="filter query string")
1000             port_action_detach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
1001             port_action_detach_parser.set_defaults(func=element_action_edit, element="ports", action="dettach")
1002
1003         if item=='net' or item=='host':
1004             nethost_action_up_parser = subparsers.add_parser(item+"-up", help="puts admin_state_up of "+item_dict[item][:-1]+" to true")
1005             nethost_action_up_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1])
1006             nethost_action_up_parser.add_argument("-F","--filter", action="store", help="filter query string")
1007             nethost_action_up_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
1008             nethost_action_up_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="up")
1009         
1010             nethost_action_down_parser = subparsers.add_parser(item+"-down", help="puts admin_state_up of "+item_dict[item][:-1]+" to false")
1011             nethost_action_down_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1])
1012             nethost_action_down_parser.add_argument("-F","--filter", action="store", help="filter query string")
1013             nethost_action_down_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
1014             nethost_action_down_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="down")
1015
1016     #openflow rules
1017     openflow_list_action = subparsers.add_parser("openflow-port-list", help="list openflow switch ports name")
1018     openflow_list_action.set_defaults(func=openflow_action, action="port-list")
1019
1020     openflow_list_action = subparsers.add_parser("openflow-port-mapping-list", help="list computes port mapping")
1021     openflow_list_action.set_defaults(func=openflow_action, action="port-mapping")
1022     
1023     openflow_list_action = subparsers.add_parser("openflow-clear-all", help="removes all openflow rules")
1024     openflow_list_action.set_defaults(func=openflow_action, action="clear-all")
1025
1026     openflow_list_action = subparsers.add_parser("openflow-net-reinstall", help="reinstall the openflow rules for a network")
1027     openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks")
1028     openflow_list_action.set_defaults(func=openflow_action, action="reinstall")
1029     
1030     openflow_list_action = subparsers.add_parser("openflow-net-list", help="list installed openflow rules for a network")
1031     openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks")
1032     openflow_list_action.set_defaults(func=openflow_action, action="rules-list")
1033
1034     argcomplete.autocomplete(main_parser)
1035     
1036     try:
1037         args = main_parser.parse_args()
1038         result = args.func(args)
1039         if result == None:
1040             result = 0
1041         #for some reason it fails if call exit inside try instance. Need to call exit at the end !?
1042     except (requests.exceptions.ConnectionError):
1043         print "Connection error: not possible to contact OPENVIM-SERVER (openvimd)"
1044         result = -2
1045     except (KeyboardInterrupt):
1046         print 'Exiting openVIM'
1047         result = -3
1048     except (SystemExit, ArgumentParserError):
1049         result = -4
1050     
1051     #print result
1052     exit(result)
1053