X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=openvim;fp=openvim;h=ae49e291c6bed2eb6304983de7de37f8b34b38b3;hb=f7aa8c4db7a57d5865d3b7767d5957fda6867198;hp=0000000000000000000000000000000000000000;hpb=de6d6e77ff7bb93136a01ca8d3b90be9bc4be013;p=osm%2Fopenvim.git diff --git a/openvim b/openvim new file mode 100755 index 0000000..ae49e29 --- /dev/null +++ b/openvim @@ -0,0 +1,1047 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# PYTHON_ARGCOMPLETE_OK + +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +''' +This is a client for managing the openvim server. +Useful for human CLI management of items at openvim +''' +__author__="Alfonso Tierno, Gerardo Garcia" +__date__ ="$26-nov-2014 19:09:29$" +__version__="0.4.0-r443" +version_date="Nov 2015" +name="openvim" + +from argcomplete.completers import FilesCompleter +import os +import argparse +import argcomplete +import requests +import json +import yaml +from jsonschema import validate as js_v, exceptions as js_e + +class ArgumentParserError(Exception): pass + +class ThrowingArgumentParser(argparse.ArgumentParser): + def error(self, message): + print "Error: %s" %message + print + self.print_usage() + #self.print_help() + print + print "Type 'openvim -h' for help" + raise ArgumentParserError + +global vim_config +config_items=("HOST", "PORT", "ADMIN_PORT", "TENANT") +show_at_verbose1=('status', 'created', 'path', 'last_error','description','hostId','progress', + 'ram','vcpus','type','shared','admin_state_up', 'enabled', 'ip_name') + +#template for creating new items +template={ + "port":{ + "${}":[ + "${name} provide a port name", + "${net_id} provide the network uuid (null):", + "${vlan} provide the vlan if any (null):", + "${port} provide the attached switch port (Te0/47)", + "${mac} provide the mac of external device if known (null):" + ], + "port":{ + "name": "${name}", + "network_id": "${net_id null}", + "type": "external", + "binding:vlan": "${vlan null-int}", + "binding:switch_port": "${port}", + "mac_address": "${mac null}" + }, + }, + "network":{ + "${}":[ + "${name} provide a network name", + "${type} provide a type: bridge_data,bridge_man,data,ptp (data)", + "${shared} external network: true,false (true)", + "${phy} conected to: bridge:name,macvtap:iface,default (null)" + ], + "network":{ + "name": "${name}", + "type": "${type}", + "provider:physical": "${phy null}", + "shared": "${shared bool}" + } + }, + "host": { + "${}":[ + "${name} host name", + "${user} host user (user)", + "${ip_name} host access IP or name (${name})", + "${description} host description (${name})" + ], + + "host":{ + "name": "${name}", + "user": "${user}", + "ip_name": "${ip_name}", + "description": "${description}" + } + }, + "flavor":{ + "${}":[ + "${name} flavor name", + "${description} flavor description (${name})", + "${processor_ranking} processor ranking (100)", + "${memory} memory in GB (2)", + "${threads} threads needed (2)" + ], + "flavor":{ + "name":"${name}", + "description":"${description}", + "extended":{ + "processor_ranking":"${processor_ranking int}", + "numas":[ + { + "memory":"${memory int}", + "threads":"${threads int}" + } + ] + } + } + }, + "tenant":{ + "${}":[ + "${name} tenant name", + "${description} tenant description (${name})" + ], + "tenant":{ + "name": "${name}", + "description": "${description}" + } + }, + "image":{ + "${}":[ + "${name} image name", + "${path} image path (/path/to/shared/compute/host/folder/image.qcow2)", + "${description} image description (${name})" + ], + "image":{ + "name":"${name}", + "description":"${description}", + "path":"${path}" + } + }, + "server":{ + "${}":[ + "${name} provide a name (VM)", + "${description} provide a description (${name})", + "${image_id} provide image_id uuid", + "${flavor_id} provide flavor_id uuid", + "${network0} provide a bridge network id; enter='default' (00000000-0000-0000-0000-000000000000)", + "${network1} provide a bridge network id; enter='virbrMan' (60f5227e-195f-11e4-836d-52540030594e)" + ], + "server":{ + "networks":[ + { + "name":"mgmt0", + "vpci": "0000:00:0a.0", + "uuid":"${network0}" + }, + { + "name":"ge0", + "vpci": "0000:00:0b.0", + "uuid":"${network1}" + } + ], + "name":"${name}", + "description":"${description}", + "imageRef": "${image_id}", + "flavorRef": "${flavor_id}" + } + } +} + +def check_configuration(*exception ): + ''' + Check that the configuration variables are present. + exception can contain variables that are not tested, normally because is not used for the command + ''' + #print exception + for item in config_items: + if item not in exception and vim_config[item]==None: + print "OPENVIM_"+item+" variable not defined. Try '" + name + " config file_cfg' or 'export OPENVIM_"+item+"=something'" + exit(-401); #HTTP_Unauthorized + +def check_valid_uuid(uuid): + ''' + Determines if the param uuid is a well formed uuid. Otherwise it is consider a name + ''' + id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"} + try: + js_v(uuid, id_schema) + return True + except js_e.ValidationError: + return False + +def _print_verbose(row_data, element, verbose=0): + #print row_data + data = row_data[element] + if verbose==1 or verbose==2: + data2= dict((k,v) for (k,v) in data.iteritems() if k in show_at_verbose1 and v!=None) + if 'image' in data and 'id' in data['image']: + data2['image'] = data['image']['id'] + if 'flavor' in data and 'id' in data['flavor']: + data2['flavor'] = data['flavor']['id'] + if verbose==2: + #TODO add more intems in a digest mode, extended, numas ... + pass + #if "numas" in data + #if "extended" in data + else: + data2= data + #print json.dumps(c2, indent=4) +# keys = data.keys() +# for key in keys: +# if data[key]==None: del data[key] +# elif key=='id' or key=='name' or key=='links': del data[key] + + #print json.dumps(data2, indent=4) + data2={element: data2} + print yaml.safe_dump(data2, indent=4, default_flow_style=False) + +def vim_read(url): + ''' + Send a GET http to VIM + ''' + headers_req = {'content-type': 'application/json'} + try: + vim_response = requests.get(url, headers = headers_req) + if vim_response.status_code == 200: + #print vim_response.json() + #print json.dumps(vim_response.json(), indent=4) + content = vim_response.json() + return 1, content + #print http_content + else: + #print " Error. VIM response '%s': not possible to GET %s, error %s" % (vim_response.status_code, url, vim_response.text) + return -vim_response.status_code, vim_response.text + except requests.exceptions.RequestException, e: + return -1, " Exception GET at '"+url+"' " + str(e.message) + +def vim_delete(url): + ''' + Send a DELETE http to VIM + ''' + headers_req = {'content-type': 'application/json'} + try: + vim_response = requests.delete(url, headers = headers_req) + if vim_response.status_code != 200: + #print " Error. VIM response '%s': not possible to DELETE %s, error %s" % (vim_response.status_code, url, vim_response.text) + return -vim_response.status_code, vim_response.text + except requests.exceptions.RequestException, e: + return -1, " Exception DELETE at '"+url+"' " + str(e.message) + return 1, vim_response.json() + +def vim_action(url, payload): + ''' + Send a POST http to VIM + ''' + headers_req = {'content-type': 'application/json'} + try: + vim_response = requests.post(url, data=json.dumps(payload), headers = headers_req) + if vim_response.status_code != 200: + #print " Error. VIM response '%s': not possible to POST %s, error %s" % (vim_response.status_code, url, vim_response.text) + return -vim_response.status_code, vim_response.text + except requests.exceptions.RequestException, e: + return -1, " Exception POST at '"+url+"' " + str(e.message) + return 1, vim_response.json() + +def vim_edit(url, payload): + headers_req = {'content-type': 'application/json'} + try: + vim_response = requests.put(url, data=json.dumps(payload), headers = headers_req) + if vim_response.status_code != 200: + #print " Error. VIM response '%s': not possible to PUT %s, error %s" % (vim_response.status_code, url, vim_response.text) + return -vim_response.status_code, vim_response.text + except requests.exceptions.RequestException, e: + return -1, " Exception PUT at '"+url+"' " + str(e.message) + return 1, vim_response.json() + +def vim_create(url, payload): + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + #print str(payload) + try: + vim_response = requests.post(url, data=json.dumps(payload), headers=headers_req) + if vim_response.status_code != 200: + #print " Error. VIM response '%s': not possible to POST %s, error %s" % (vim_response.status_code, url, vim_response.text) + return -vim_response.status_code, vim_response.text + except requests.exceptions.RequestException, e: + return -1, " Exception POST at '"+url+"' " + str(e.message) + return 1, vim_response.json() + +def parse_yaml_json(text, file_name=None): + parser_json=None + if file_name: + if file_name[-5:]=='.yaml' or file_name[-4:]=='.yml': + parser_json = False + elif file_name[-5:]=='.json': + parser_json = True + if parser_json==None: + parser_json=True if '\t' in text else False + + if parser_json: #try parse in json format, because yaml does not admit tabs + try: + data = json.loads(text) + return 0, data + except Exception as e: + return -1, "Error json format: " + str(e) + #try to parse in yaml format + try: + data = yaml.load(text) + return 0, data + except yaml.YAMLError as exc: + error_pos = "" + if hasattr(exc, 'problem_mark'): + mark = exc.problem_mark + error_pos = " at line:%s column:%s" % (mark.line+1, mark.column+1) + return -1, " Error yaml format error" + error_pos + +def change_string(text, var_list): + ''' + See change_var_recursively help + Used for changed any '${var format}' content inside the 'text' string + into the corresponding value at 'var_list'[var] + 'format' is optional, string by default. Contain a - separated formats,ej, int-null + ''' + end=0 + type_=None + while True: + ini = text.find("${", end) + if ini<0: return text + end = text.find("}", ini) + if end<0: return text + end+=1 + + var = text[ini:end] + if ' ' in var: + kk=var.split(" ") + var=kk[0]+"}" + type_=kk[-1][:-1] + var = var_list.get(var, None) + if var==None: return text + + text = text[:ini] + var + text[end:] + if type_ != None: + if 'null' in type_ and text=="null": + return None + if 'int' in type_ : #and text.isnumeric(): + return int(text) + if 'bool' in type_ : #and text.isnumeric(): + if text.lower()=="true": return True + elif text.lower()=="false": return False + else: + print "input boolean paramter must be 'true' or 'false'" + exit(1) + return text + +def chage_var_recursively(data, var_list): + ''' + Check recursively the content of 'data', and look for "*${*}*" variables and changes + It assumes that this variables are not in the key of dictionary, + The overall target is having json/yaml templates with this variables in the text. + The user is asked for a value that is replaced wherever appears + Attributes: + 'data': dictionary, or list. None or empty is considered valid + 'var_list': dictionary (name:change) pairs + Return: + None, data is modified + ''' + + if type(data) is dict: + for k in data.keys(): + if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list: + chage_var_recursively(data[k], var_list) + elif type(data[k]) is str: + data[k] = change_string(data[k], var_list) + if type(data) is list: + for k in range(0,len(data)): + if type(data[k]) is dict or type(data[k]) is list: + chage_var_recursively(data[k], var_list) + elif type(data[k]) is str: + data[k] = change_string(data[k], var_list) + +def change_var(data, default_values={}): + ''' Look for a text "${}" key at 'data' that indicates that this json contains + variables that must be ask for values to the user and changes in the text of + the dictionary + 'default_values' contain a dictionary of values that must be used and not be asked + Useful for creating templates + "${}" entry contain a list with the text: + ${var-name} prompt text to show user (Default value to allocate if user type CR) + ''' + if type(data) is not dict: + return -1, "Format error, not a object (dictionary)" + if "${}" not in data: + return 0, data + + var_list={} + for var in data["${}"]: + r = var.find("}",) + 1 + if r<=2 or var[:2] != '${': + return -1, "Format error at '${}':" + var + #change variables inside description text + if "${" in var[r:]: + var = var[:r] + change_string(var[r:], var_list) + d_start = var.rfind("(",) + 1 + d_end = var.rfind(")",) + if d_start>0 and d_end>=d_start: + default = var[d_start:d_end] + else: default=None + if var[2:r-1] in default_values: + var_list[ var[:r] ] = default_values[ var[2:r-1] ] + continue + v = raw_input(var[r:] + "? ") + if v=="": + if default != None: + v = default + else: + v = raw_input(" empty string? try again: ") + var_list[ var[:r] ] = str(v) + del data["${}"] + chage_var_recursively(data, var_list) + return 0, data + +def load_file(file_, parse=False): + try: + f = open(file_, 'r') + read_data = f.read() + f.close() + if not parse: + return 0, read_data + except IOError, e: + return -1, " Error opening file '" + file_ + "': " + e.args[1] + return parse_yaml_json(read_data, file_) + +def load_file_or_yaml(content): + ''' + 'content' can be or a yaml/json file or a text containing a yaml/json text format + This function autodetect, trying to load and parse the filename 'content', + if fails trying to parse the 'content' as text + Returns the dictionary once parsed, or print an error and finish the program + ''' + r,payload = load_file(content, parse=True) + if r<0: + if r==-1 and "{" in content or ":" in content: + #try to parse directly + r,payload = parse_yaml_json(content) + if r<0: + print payload + exit (-1) + else: + print payload + exit (-1) + return payload + +def config(args): + #print "config-list",args + if args.file != None: + try: + f = open(args.file, 'r') + read_data = f.read() + f.close() + except IOError, e: + print " Error opening file '" + args.file + "': " + e.args[1] + return -1 + try: + data = yaml.load(read_data) + #print data + if "http_host" in data: + print " export OPENVIM_HOST="+ data["http_host"] + if "http_port" in data: + print " export OPENVIM_PORT="+ str(data["http_port"]) + #vim_config[item] = data["OPENVIM_"+item] #TODO find the way to change envioronment + #os.setenv('OPENVIM_'+item, vim_config[item]) + if "http_admin_port" in data: + print " export OPENVIM_ADMIN_PORT="+ str(data["http_admin_port"]) + if "tenant_id" in data: + print " export OPENVIM_TENANT="+ data["tenant_id"] + return 0 + except yaml.YAMLError, exc: + error_pos = "" + if hasattr(exc, 'problem_mark'): + mark = exc.problem_mark + error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1) + print " Error yaml/json format error at '"+ args.file +"'"+error_pos + return -1 + print args.file + print "OPENVIM_HOST: %s" %vim_config["HOST"] + print "OPENVIM_PORT: %s" %vim_config["PORT"] + print "OPENVIM_ADMIN_PORT: %s" %vim_config["ADMIN_PORT"] + print "OPENVIM_TENANT: %s" %vim_config["TENANT"] + return 0 + +def element_new(args): + #print args + tenant="" + if args.element in ('flavors','images','servers'): + check_configuration( "ADMIN_PORT" ) + tenant="/"+vim_config["TENANT"] + else: + check_configuration("ADMIN_PORT", "TENANT") + tenant="" + + default_values={} + if args.name != None: + default_values["name"] = args.name + if "description" in args and args.description != None: + default_values["description"] = args.description + if "path" in args and args.path != None: + default_values["path"] = args.path + if args.file==None: + payload= template[args.element[:-1] ] + payload = yaml.load(str(payload)) #with this trick we make a completely copy of the data, so to not modified the original one + else: + payload=load_file_or_yaml(args.file) + r,c= change_var(payload, default_values) + if r<0: + print "Template error", c + return -1 + payload=c + #print payload + if args.element[:-1] not in payload: + payload = {args.element[:-1]: payload } + item = payload[args.element[:-1]] + + url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element) + if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None: + url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element) + else: + url_admin = None + + if args.name != None: + item["name"] = args.name + if "description" in args and args.description != None: + item["description"] = args.description + if "path" in args and args.path != None: + item["path"] = args.path + + + r,c = vim_create(url, payload) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + url=url_admin + r,c = vim_create(url, payload) + if r<0: + print c + return -r + else: + #print c + item=c[ args.element[:-1] ] + uuid=item.get('id', None) + if uuid is None: + uuid=item.get('uuid', 'uuid-not-found?') + name = item.get('name', '') + print " " + uuid +" "+ name.ljust(20) + " Created" + for e in ('warning', 'error', 'last_error'): + if e in item: print e + "\n" + item[e] + if args.verbose!=None: + _print_verbose(c, args.element[:-1], args.verbose) + return 0 + +def element_action(args): + filter_qs = "" + tenant="" + if args.element in ('flavors','images','servers'): + check_configuration( "ADMIN_PORT") + tenant="/"+vim_config["TENANT"] + else: + check_configuration("ADMIN_PORT", "TENANT") + tenant="" + url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element) + if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None: + url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element) + else: + url_admin = None + + if args.filter: + filter_qs += "?" + args.filter + if args.name!=None: + if check_valid_uuid(args.name): + if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name) + else: filter_qs += "?" + "id=" + str(args.name) + else: + if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name) + else: filter_qs += "?" + "name=" + str(args.name) + + + r,c = vim_read(url + filter_qs) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + r,c = vim_read(url_admin + filter_qs) + if r<0: + print "Error:", c + return -r + if args.action=='createImage': + payload={ args.action: {"name":args.imageName} } + if args.description != None: + payload[args.action]["description"]=args.description + if args.path != None: + payload[args.action]["path"]=args.path + else: + payload={ args.action: None} + #print json.dumps(c, indent=4) + item_list = c[ args.element ] + if len(item_list)==0 and args.name != None: + print " Not found " + args.element + " " + args.name + return 404 #HTTP_Not_Found + result = 0 + for item in item_list: + uuid=item.get('id', None) + if uuid is None: + uuid=item.get('uuid', None) + if uuid is None: + print "Id not found" + continue + name = item.get('name', '') + if not args.force: + r = raw_input(" Action over " + args.element + " " + uuid + " " + name + " (y/N)? ") + if len(r)>0 and r[0].lower()=="y": + pass + else: + continue + r,c = vim_action(url + "/" + uuid + "/action", payload) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + url=url_admin + r,c = vim_action(url + "/" + uuid + "/action", payload) + if r<0: + print " " + uuid +" "+ name.ljust(20) + " " + c + result = -r + else: + if args.action == "createImage": #response contain an {image: {...} }, not a {server: {...} }. + print " " + c["image"]["id"] +" "+ c["image"]["name"].ljust(20) + args.element="images" + else: + print " " + uuid +" "+ name.ljust(20) + " "+ args.action + if "verbose" in args and args.verbose!=None: + _print_verbose(c, args.element[:-1], args.verbose) + return result + +def element_edit(args): + filter_qs = "" + tenant="" + if args.element in ('flavors','images','servers'): + check_configuration( "ADMIN_PORT") + tenant="/"+vim_config["TENANT"] + else: + check_configuration("ADMIN_PORT", "TENANT") + tenant="" + + url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element) + if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None: + url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element) + else: + url_admin = None + + if args.filter: + filter_qs += "?" + args.filter + if args.name!=None: + if check_valid_uuid(args.name): + if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name) + else: filter_qs += "?" + "id=" + str(args.name) + else: + if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name) + else: filter_qs += "?" + "name=" + str(args.name) + + + r,c = vim_read(url + filter_qs) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + r,c = vim_read(url_admin + filter_qs) + if r<0: + print "Error:", c + return -r + + payload=load_file_or_yaml(args.file) + r2,c2= change_var(payload) + if r2<0: + print "Template error", c2 + return -1 + payload=c2 + if args.element[:-1] not in payload: + payload = {args.element[:-1]: payload } + + #print json.dumps(c, indent=4) + item_list = c[ args.element ] + if len(item_list)==0 and args.name != None: + print " Not found " + args.element + " " + args.name + return 404 #HTTP_Not_Found + result = 0 + for item in item_list: + uuid=item.get('id', None) + if uuid is None: + uuid=item.get('uuid', None) + if uuid is None: + print "Id not found" + continue + name = item.get('name', '') + if not args.force or (args.name==None and args.filer==None): + r = raw_input(" Edit " + args.element + " " + uuid + " " + name + " (y/N)? ") + if len(r)>0 and r[0].lower()=="y": + pass + else: + continue + r,c = vim_edit(url + "/" + uuid, payload) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + url=url_admin + r,c = vim_edit(url + "/" + uuid, payload) + if r<0: + print " " + uuid +" "+ name.ljust(20) + " " + c + result = -r + else: + print " " + uuid +" "+ name.ljust(20) + " edited" + if "verbose" in args and args.verbose!=None: + _print_verbose(c, args.element[:-1], args.verbose) + return result + +def element_action_edit(args): + #print args + if args.element=='ports': + if args.action=='attach': + args.file='network_id: ' + args.network_id + else: #args.action=='detach' + args.file='network_id: null' + if args.action=='up': + args.file='admin_state_up: true' + if args.action=='down': + args.file='admin_state_up: false' + return element_edit(args) + +def element_delete(args): + filter_qs = "" + tenant="" + if args.element in ('flavors','images','servers'): + check_configuration("ADMIN_PORT" ) + tenant="/"+vim_config["TENANT"] + else: + check_configuration("ADMIN_PORT", "TENANT") + tenant="" + + url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element) + if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None: + url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element) + else: + url_admin = None + + if args.filter: + filter_qs += "?" + args.filter + if args.name!=None: + if check_valid_uuid(args.name): + if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name) + else: filter_qs += "?" + "id=" + str(args.name) + else: + if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name) + else: filter_qs += "?" + "name=" + str(args.name) + + + r,c = vim_read(url + filter_qs) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + r,c = vim_read(url_admin + filter_qs) + if r<0: + print "Error:", c + return -r + + #print json.dumps(c, indent=4) + item_list = c[ args.element ] + if len(item_list)==0 and args.name != None: + print " Not found " + args.element + " " + args.name + return 404 #HTTP_Not_Found + result = 0 + for item in item_list: + uuid=item.get('id', None) + if uuid is None: + uuid=item.get('uuid', None) + if uuid is None: + print "Id not found" + result = -500 + continue + name = item.get('name', '') + if not args.force: + r = raw_input(" Delete " + args.element + " " + uuid + " " + name + " (y/N)? ") + if len(r)>0 and r[0].lower()=="y": + pass + else: + continue + r,c = vim_delete(url + "/" + uuid) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + url=url_admin + r,c = vim_delete(url + "/" + uuid) + if r<0: + print " " + uuid +" "+ name.ljust(20) + " " + c + result = -r + else: + print " " + uuid +" "+ name.ljust(20) + " deleted" + return result + +def element_list(args): + #print "element_list", args + filter_qs = "" + tenant="" + if args.element in ('flavors','images','servers'): + check_configuration( "ADMIN_PORT" ) + tenant="/"+vim_config["TENANT"] + else: + check_configuration( "ADMIN_PORT", "TENANT" ) + tenant="" + #if args.name: + # what += "/" + args.name + url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element) + if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None: + url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element) + else: + url_admin = None + #print " get", what, " >>>>>>>> ", + if args.filter: + filter_qs += "?" + args.filter + if args.name!=None: + if check_valid_uuid(args.name): + if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name) + else: filter_qs += "?" + "id=" + str(args.name) + else: + if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name) + else: filter_qs += "?" + "name=" + str(args.name) + + + r,c = vim_read(url + filter_qs) + if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + r,c = vim_read(url_admin + filter_qs) + if r<0: + print "Error:", c + return -r + #print json.dumps(c, indent=4) + result = 0 + item_list = c[ args.element ] + verbose=0 + #if args.name!=None and len(item_list)==1: + # verbose+=1 + if args.verbose!=None: + verbose += args.verbose + for item in item_list: + extra="" + if args.element=="servers" or args.element=="networks": extra = " "+item['status'] + if args.element in ("hosts","networks","ports") and not item['admin_state_up']: extra += " admin_state_up=false" + print item['id']+" "+item['name'].ljust(20) + extra + if verbose>0: + r2,c2 = vim_read(url + "/"+ item['id']) + if r2==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges + url=url_admin + r2,c2 = vim_read(url + "/"+ item['id']) + if r2>0: + _print_verbose(c2, args.element[:-1], verbose) + + return result + +def openflow_action(args): + if args.action=='port-list': + url = "http://%s:%s/openvim/networks/openflow/ports" %(vim_config["HOST"], vim_config["PORT"]) + r,c = vim_read(url) + elif args.action=='rules-list' or args.action=='reinstall': + PORT = vim_config["PORT"] + if args.action=='reinstall': + if "ADMIN_PORT" not in vim_config: + print "OPENVIM_ADMIN_PORT variable not defined" + return 404 #HTTP_Not_Found + PORT = vim_config["ADMIN_PORT"] + if args.name!=None: + url = "http://%s:%s/openvim/networks" %(vim_config["HOST"], PORT) + if check_valid_uuid(args.name): + url += "?id=" + str(args.name) + else: + url += "?name=" + str(args.name) + r,c = vim_read(url) + if r<0: + print "Error:", c + return -r + if len (c["networks"]) == 0: + print " Network not found" + return 404 #HTTP_Not_Found + if len (c["networks"]) > 1: + print " More than one net with this name found. Use uuid instead of name to concretize" + return 404 #HTTP_Not_Found + network = c["networks"][0]["id"] + else: + network="all" + url = "http://%s:%s/openvim/networks/%s/openflow" %(vim_config["HOST"], PORT, network) + if args.action=='reinstall': + r,c = vim_edit(url, None) + else: + r,c = vim_read(url) + elif args.action=='clear-all': + if "ADMIN_PORT" not in vim_config: + print "OPENVIM_ADMIN_PORT variable not defined" + return 401 # HTTP_Unauthorized + url = "http://%s:%s/openvim/networks/openflow/clear" %(vim_config["HOST"], vim_config["ADMIN_PORT"]) + r,c = vim_delete(url) + else: + return 400 #HTTP_Bad_Request + if r<0: + print "Error:", c + return -r + else: + print yaml.safe_dump(c, indent=4, default_flow_style=False) + return 0 + +if __name__=="__main__": + + item_dict={'vm':'servers','host':'hosts','tenant':'tenants','image':'images','flavor':'flavors','net':'networks','port':'ports'} + + vim_config={} + vim_config["HOST"] = os.getenv('OPENVIM_HOST', 'localhost') + vim_config["PORT"] = os.getenv('OPENVIM_PORT', '9080') + vim_config["ADMIN_PORT"] = os.getenv('OPENVIM_ADMIN_PORT', '9085') + vim_config["TENANT"] = os.getenv('OPENVIM_TENANT', None) + + + main_parser = ThrowingArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)') + #main_parser = argparse.ArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)') + main_parser.add_argument('--version', action='version', version='%(prog)s ' + __version__ + ' '+version_date) + + subparsers = main_parser.add_subparsers(help='commands') + + config_parser = subparsers.add_parser('config', help="prints configuration values") + config_parser.add_argument("file", nargs='?', help="configuration file to extract the configuration").completer = FilesCompleter + config_parser.set_defaults(func=config) + #HELP + for item in item_dict: + #LIST + element_list_parser = subparsers.add_parser(item+'-list', help="lists information about "+item_dict[item]) + element_list_parser.add_argument("name", nargs='?', help="name or ID of the " + item) + element_list_parser.add_argument("-F","--filter", action="store", help="filter query string") + element_list_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity") + element_list_parser.set_defaults(func=element_list, element=item_dict[item]) + #NEW + if item=='host': + element_new_parser = subparsers.add_parser(item+'-add', help="adds a new compute node") + else: + element_new_parser = subparsers.add_parser(item+'-create', help="creates a new "+item_dict[item][:-1]) + element_new_parser.add_argument("file", nargs='?', help="json/yaml text or file with content").completer = FilesCompleter + element_new_parser.add_argument("--name", action="store", help="Use this name") + if item!="network": + element_new_parser.add_argument("--description", action="store", help="Use this descrition") + if item=="image": + element_new_parser.add_argument("--path", action="store", help="Use this path") + element_new_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity") + element_new_parser.set_defaults(func=element_new, element=item_dict[item]) + #DELETE + if item=='host': + element_del_parser = subparsers.add_parser(item+'-remove', help="removes a compute node") + else: + element_del_parser = subparsers.add_parser(item+'-delete', help="deletes one or several "+item_dict[item]) + element_del_parser.add_argument("name", nargs='?', help="name or ID of the "+item+", if missing means all") + element_del_parser.add_argument("-F","--filter", action="store", help="filter query string") + element_del_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + element_del_parser.set_defaults(func=element_delete, element=item_dict[item]) + #EDIT + element_edit_parser = subparsers.add_parser(item+'-edit', help="edits one or several "+item_dict[item]) + element_edit_parser.add_argument("name", help="name or ID of the "+item+"") + element_edit_parser.add_argument("file", help="json/yaml text or file with the changes").completer = FilesCompleter + element_edit_parser.add_argument("-F","--filter", action="store", help="filter query string") + element_edit_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + element_edit_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity") + element_edit_parser.set_defaults(func=element_edit, element=item_dict[item]) + #ACTION + if item=='vm': + for item2 in ('shutdown', 'start', 'rebuild', 'reboot'): + vm_action_parser = subparsers.add_parser("vm-"+item2, help="performs this action over the virtual machine") + vm_action_parser.add_argument("name", nargs='?', help="name or ID of the server, if missing means all") + vm_action_parser.add_argument("-F","--filter", action="store", help="filter query string") + vm_action_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + vm_action_parser.set_defaults(func=element_action, element="servers", action=item2 ) + vm_action_image_parser = subparsers.add_parser("vm-createImage", help="creates a snapshot of the virtual machine disk into a new image") + vm_action_image_parser.add_argument("name", help="name or ID of the server") + vm_action_image_parser.add_argument("imageName", help="image name") + vm_action_image_parser.add_argument("--description", action="store", help="Provide a new image description") + vm_action_image_parser.add_argument("--path", action="store", help="Provide a new image complete path") + vm_action_image_parser.add_argument("-F","--filter", action="store", help="filter query string") + vm_action_image_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + vm_action_image_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity") + vm_action_image_parser.set_defaults(func=element_action, element="servers", action="createImage" ) + #ACTION that are implemented with EDITION + if item=='port': + port_action_attach_parser = subparsers.add_parser("port-attach", help="connects a port to a network") + port_action_attach_parser.add_argument("name", help="name or ID of the port") + port_action_attach_parser.add_argument("network_id", help="ID of the network") + port_action_attach_parser.add_argument("-F","--filter", action="store", help="filter query string") + port_action_attach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + port_action_attach_parser.set_defaults(func=element_action_edit, element="ports", action="attach") + + port_action_detach_parser = subparsers.add_parser("port-detach", help="removes a port from a network") + port_action_detach_parser.add_argument("name", help="name or ID of the port") + port_action_detach_parser.add_argument("-F","--filter", action="store", help="filter query string") + port_action_detach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + port_action_detach_parser.set_defaults(func=element_action_edit, element="ports", action="dettach") + + if item=='net' or item=='host': + nethost_action_up_parser = subparsers.add_parser(item+"-up", help="puts admin_state_up of "+item_dict[item][:-1]+" to true") + nethost_action_up_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1]) + nethost_action_up_parser.add_argument("-F","--filter", action="store", help="filter query string") + nethost_action_up_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + nethost_action_up_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="up") + + nethost_action_down_parser = subparsers.add_parser(item+"-down", help="puts admin_state_up of "+item_dict[item][:-1]+" to false") + nethost_action_down_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1]) + nethost_action_down_parser.add_argument("-F","--filter", action="store", help="filter query string") + nethost_action_down_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") + nethost_action_down_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="down") + + #openflow rules + openflow_list_action = subparsers.add_parser("openflow-port-list", help="list openflow switch ports name") + openflow_list_action.set_defaults(func=openflow_action, action="port-list") + + openflow_list_action = subparsers.add_parser("openflow-clear-all", help="removes all openflow rules") + openflow_list_action.set_defaults(func=openflow_action, action="clear-all") + + openflow_list_action = subparsers.add_parser("openflow-net-reinstall", help="reinstall the openflow rules for a network") + openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks") + openflow_list_action.set_defaults(func=openflow_action, action="reinstall") + + openflow_list_action = subparsers.add_parser("openflow-net-list", help="list installed openflow rules for a network") + openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks") + openflow_list_action.set_defaults(func=openflow_action, action="rules-list") + + argcomplete.autocomplete(main_parser) + + try: + args = main_parser.parse_args() + result = args.func(args) + if result == None: + result = 0 + #for some reason it fails if call exit inside try instance. Need to call exit at the end !? + except (requests.exceptions.ConnectionError): + print "Connection error: not possible to contact OPENVIM-SERVER (openvimd)" + result = -2 + except (KeyboardInterrupt): + print 'Exiting openVIM' + result = -3 + except (SystemExit, ArgumentParserError): + result = -4 + + #print result + exit(result) +