#!/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 openvim
# 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)

