From 5cf7dc6cb262f4b9435a052b9244d6cb5b023dc5 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Thu, 2 Jun 2011 22:30:37 -0700 Subject: [PATCH] Initial rework of cli to use the WS api - Still need to implement the interface commands and also address TODO's in the code. --- quantum/api/views/ports.py | 7 +- quantum/cli.py | 469 ++++++++++++++++++++++++++++++------- 2 files changed, 389 insertions(+), 87 deletions(-) diff --git a/quantum/api/views/ports.py b/quantum/api/views/ports.py index 2d93a35f6..6077214b6 100644 --- a/quantum/api/views/ports.py +++ b/quantum/api/views/ports.py @@ -37,12 +37,13 @@ class ViewBuilder(object): else: port = self._build_simple(port_data) return port - + def _build_simple(self, port_data): """Return a simple model of a server.""" return dict(port=dict(id=port_data['port-id'])) - + def _build_detail(self, port_data): """Return a simple model of a server.""" return dict(port=dict(id=port_data['port-id'], - state=port_data['port-state'])) + attachment=port_data['attachment'], + state=port_data['port-state'])) diff --git a/quantum/cli.py b/quantum/cli.py index 78f1a6b48..faa92f1b1 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011, Nicira Networks, Inc. +# Copyright 2011 Nicira Networks, Inc. +# Copyright 2011 Citrix Systems # # 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 @@ -14,97 +15,397 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Somik Behera, Nicira Networks, Inc. +# @author: Brad Hall, Nicira Networks, Inc. +import httplib +import logging as LOG +import simplejson +import socket import sys +import urllib from manager import QuantumManager +from optparse import OptionParser +from quantum.common.wsgi import Serializer +FORMAT = "json" +CONTENT_TYPE = "application/" + FORMAT -def usage(): - print "\nUsage:" - print "list_nets " - print "create_net " - print "delete_net " - print "detail_net " - print "rename_net " - print "list_ports " - print "create_port " - print "delete_port " - print "detail_port " - print "plug_iface " - print "unplug_iface " - print "detail_iface " - print "list_iface \n" +### --- Miniclient (taking from the test directory) +### TODO(bgh): move this to a library within quantum +class MiniClient(object): + """A base client class - derived from Glance.BaseClient""" + action_prefix = '/v0.1/tenants/{tenant_id}' + def __init__(self, host, port, use_ssl): + self.host = host + self.port = port + self.use_ssl = use_ssl + self.connection = None + def get_connection_type(self): + if self.use_ssl: + return httplib.HTTPSConnection + else: + return httplib.HTTPConnection + def do_request(self, tenant, method, action, body=None, + headers=None, params=None): + action = MiniClient.action_prefix + action + action = action.replace('{tenant_id}',tenant) + if type(params) is dict: + action += '?' + urllib.urlencode(params) + try: + connection_type = self.get_connection_type() + headers = headers or {} + # Open connection and send request + c = connection_type(self.host, self.port) + c.request(method, action, body, headers) + res = c.getresponse() + status_code = self.get_status_code(res) + if status_code in (httplib.OK, httplib.CREATED, + httplib.ACCEPTED, httplib.NO_CONTENT): + return res + else: + raise Exception("Server returned error: %s" % res.read()) + except (socket.error, IOError), e: + raise Exception("Unable to connect to server. Got error: %s" % e) + def get_status_code(self, response): + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status +### -- end of miniclient -if len(sys.argv) < 2 or len(sys.argv) > 6: - usage() - exit(1) +### -- Core CLI functions -quantum = QuantumManager() -manager = quantum.get_manager() +def list_nets(manager, *args): + tenant_id = args[0] + networks = manager.get_all_networks(tenant_id) + print "Virtual Networks on Tenant:%s\n" % tenant_id + for net in networks: + id = net["net-id"] + name = net["net-name"] + print "\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name) -if sys.argv[1] == "list_nets" and len(sys.argv) == 3: - network_on_tenant = manager.get_all_networks(sys.argv[2]) - print "Virtual Networks on Tenant:%s\n" % sys.argv[2] - for k, v in network_on_tenant.iteritems(): - print"\tNetwork ID:%s \n\tNetwork Name:%s \n" % (k, v) -elif sys.argv[1] == "create_net" and len(sys.argv) == 4: - new_net_id = manager.create_network(sys.argv[2], sys.argv[3]) +def api_list_nets(client, *args): + tenant_id = args[0] + res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT) + resdict = simplejson.loads(res.read()) + LOG.debug(resdict) + print "Virtual Networks on Tenant:%s\n" % tenant_id + for n in resdict["networks"]: + net_id = n["id"] + print "\tNetwork ID:%s\n" % (net_id) + # TODO(bgh): we should make this call pass back the name too + # name = n["net-name"] + # LOG.info("\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name)) + +def create_net(manager, *args): + tid, name = args + new_net_id = manager.create_network(tid, name) print "Created a new Virtual Network with ID:%s\n" % new_net_id -elif sys.argv[1] == "delete_net" and len(sys.argv) == 4: - manager.delete_network(sys.argv[2], sys.argv[3]) - print "Deleted Virtual Network with ID:%s" % sys.argv[3] -elif sys.argv[1] == "detail_net" and len(sys.argv) == 4: - vif_list = manager.get_network_details(sys.argv[2], sys.argv[3]) - print "Remote Interfaces on Virtual Network:%s\n" % sys.argv[3] - for iface in vif_list: - print "\tRemote interface :%s" % iface -elif sys.argv[1] == "rename_net" and len(sys.argv) == 5: - manager.rename_network(sys.argv[2], sys.argv[3], sys.argv[4]) - print "Renamed Virtual Network with ID:%s" % sys.argv[3] -elif sys.argv[1] == "list_ports" and len(sys.argv) == 4: - ports = manager.get_all_ports(sys.argv[2], sys.argv[3]) - print " Virtual Ports on Virtual Network:%s\n" % sys.argv[3] - for port in ports: - print "\tVirtual Port:%s" % port -elif sys.argv[1] == "create_port" and len(sys.argv) == 4: - new_port = manager.create_port(sys.argv[2], sys.argv[3]) - print "Created Virtual Port:%s " \ - "on Virtual Network:%s" % (new_port, sys.argv[3]) -elif sys.argv[1] == "delete_port" and len(sys.argv) == 5: - manager.delete_port(sys.argv[2], sys.argv[3], sys.argv[4]) - print "Deleted Virtual Port:%s " \ - "on Virtual Network:%s" % (sys.argv[3], sys.argv[4]) -elif sys.argv[1] == "detail_port" and len(sys.argv) == 5: - port_detail = manager.get_port_details(sys.argv[2], - sys.argv[3], sys.argv[4]) - print "Virtual Port:%s on Virtual Network:%s " \ - "contains remote interface:%s" % (sys.argv[3], - sys.argv[4], - port_detail) -elif sys.argv[1] == "plug_iface" and len(sys.argv) == 6: - manager.plug_interface(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]) - print "Plugged remote interface:%s " \ - "into Virtual Network:%s" % (sys.argv[5], sys.argv[3]) -elif sys.argv[1] == "unplug_iface" and len(sys.argv) == 5: - manager.unplug_interface(sys.argv[2], sys.argv[3], sys.argv[4]) - print "UnPlugged remote interface " \ - "from Virtual Port:%s Virtual Network:%s" % (sys.argv[4], - sys.argv[3]) -elif sys.argv[1] == "detail_iface" and len(sys.argv) == 5: - remote_iface = manager.get_interface_details(sys.argv[2], - sys.argv[3], sys.argv[4]) - print "Remote interface on Virtual Port:%s " \ - "Virtual Network:%s is %s" % (sys.argv[4], - sys.argv[3], remote_iface) -elif sys.argv[1] == "list_iface" and len(sys.argv) == 4: - iface_list = manager.get_all_attached_interfaces(sys.argv[2], sys.argv[3]) - print "Remote Interfaces on Virtual Network:%s\n" % sys.argv[3] - for iface in iface_list: - print "\tRemote interface :%s" % iface -elif sys.argv[1] == "all" and len(sys.argv) == 2: - print "Not Implemented" -else: - print "invalid arguments: %s" % str(sys.argv) - usage() +def api_create_net(client, *args): + tid, name = args + data = {'network': {'network-name': '%s' % name}} + body = Serializer().serialize(data, CONTENT_TYPE) + res = client.do_request(tid, 'POST', "/networks." + FORMAT, body=body) + rd = simplejson.loads(res.read()) + LOG.debug(rd) + nid = None + try: + nid = rd["networks"]["network"]["id"] + except Exception, e: + print "Failed to create network" + # TODO(bgh): grab error details from ws request result + return + print "Created a new Virtual Network with ID:%s\n" % nid + +def delete_net(manager, *args): + tid, nid = args + manager.delete_network(tid, nid) + print "Deleted Virtual Network with ID:%s" % nid + +def api_delete_net(client, *args): + tid, nid = args + res = client.do_request(tid, 'DELETE', "/networks/" + nid + "." + FORMAT) + status = res.status + if status != 202: + print "Failed to delete network" + output = res.read() + print output + else: + print "Deleted Virtual Network with ID:%s" % nid + +def detail_net(manager, *args): + tid, nid = args + network = manager.get_network_details(tid, nid) + network_id = network["net-id"] + network_name = network["net-name"] + print "\tNetwork id:%s\n\tNetwork name:%s\n" % (network_id, network_name) + +def api_detail_net(client, *args): + tid, nid = args + res = client.do_request(tid, 'GET', "/networks/" + nid + "." + FORMAT) + output = res.read() + rd = simplejson.loads(output) + LOG.debug(rd) + network_id = rd["networks"]["network"]["id"] + network_name = rd["networks"]["network"]["name"] + print "\tNetwork id:%s\n\tNetwork name:%s\n" % (network_id, network_name) + +def rename_net(manager, *args): + tid, nid, name = args + manager.rename_network(tid, nid, name) + print "Renamed Virtual Network with ID:%s" % nid + +def api_rename_net(client, *args): + tid, nid, name = args + data = {'network': {'network-name': '%s' % name}} + body = Serializer().serialize(data, CONTENT_TYPE) + res = client.do_request(tid, 'PUT', "/networks/%s.%s" % (nid, FORMAT), + body=body) + resdict = simplejson.loads(res.read()) + LOG.debug(resdict) + print "Renamed Virtual Network with ID:%s" % nid + +# TODO(bgh): fix this command +def list_ports(manager, *args): + tid, nid = args + ports = manager.get_all_ports(tid, nid) + print "Ports on Virtual Network:%s\n" % nid + for port in ports: + "\tVirtual Port:%s" % port + +def api_list_ports(client, *args): + tid, nid = args + res = client.do_request(tid, 'GET', + "/networks/%s/ports.%s" % (nid, FORMAT)) + output = res.read() + if res.status != 200: + LOG.error("Failed to list ports: %s" % output) + return + rd = simplejson.loads(output) + LOG.debug(rd) + print "Ports on Virtual Network:%s\n" % nid + for port in rd["ports"]: + print "\tVirtual Port:%s" % port["id"] + +def create_port(manager, *args): + tid, nid = args + new_port = manager.create_port(tid, nid) + print "Created Virtual Port:%s " \ + "on Virtual Network:%s" % (new_port, nid) + +def api_create_port(client, *args): + tid, nid = args + res = client.do_request(tid, 'POST', + "/networks/%s/ports.%s" % (nid, FORMAT)) + output = res.read() + if res.status != 200: + LOG.error("Failed to create port: %s" % output) + return + rd = simplejson.loads(output) + new_port = rd["ports"]["port"]["id"] + print "Created Virtual Port:%s " \ + "on Virtual Network:%s" % (new_port, nid) + +def delete_port(manager, *args): + tid, nid, pid = args + LOG.info("Deleted Virtual Port:%s " \ + "on Virtual Network:%s" % (pid, nid)) + +def api_delete_port(client, *args): + tid, nid, pid = args + res = client.do_request(tid, 'DELETE', + "/networks/%s/ports/%s.%s" % (nid, pid, FORMAT)) + output = res.read() + if res.status != 202: + LOG.error("Failed to delete port: %s" % output) + return + LOG.info("Deleted Virtual Port:%s " \ + "on Virtual Network:%s" % (pid, nid)) + +def detail_port(manager, *args): + tid, nid, pid = args + port_detail = manager.get_port_details(tid, nid, pid) + print "Virtual Port:%s on Virtual Network:%s " \ + "contains remote interface:%s" % (pid, nid, port_detail) + +def api_detail_port(client, *args): + tid, nid, pid = args + res = client.do_request(tid, 'GET', + "/networks/%s/ports/%s.%s" % (nid, pid, FORMAT)) + output = res.read() + if res.status != 200: + LOG.error("Failed to get port details: %s" % output) + return + rd = simplejson.loads(output) + port = rd["ports"]["port"] + id = port["id"] + attachment = port["attachment"] + LOG.debug(port) + print "Virtual Port:%s on Virtual Network:%s " \ + "contains remote interface:%s" % (pid, nid, attachment) + +# TODO(bgh): still need to implement the iface commands +def plug_iface(manager, *args): + tid, nid, pid, vid = args + manager.plug_interface(tid, nid, pid, vid) + LOG.info("Plugged remote interface:%s " \ + "into Virtual Network:%s" % (vid, nid)) + +def unplug_iface(manager, *args): + tid, nid, pid = args + manager.unplug_interface(tid, nid, pid) + LOG.info("UnPlugged remote interface " \ + "from Virtual Port:%s Virtual Network:%s" % (pid, nid)) + +def detail_iface(manager, *args): + tid, nid, pid = args + remote_iface = manager.get_interface_details(tid, nid, pid) + LOG.info("Remote interface on Virtual Port:%s " \ + "Virtual Network:%s is %s" % (pid, nid, remote_iface)) + +def list_iface(manager, *args): + tid, nid = args + iface_list = manager.get_all_attached_interfaces(tid, nid) + LOG.info("Remote Interfaces on Virtual Network:%s\n" % nid) + for iface in iface_list: + LOG.info("\tRemote interface :%s" % iface) + +commands = { + "list_nets": { + "func": list_nets, + "api_func": api_list_nets, + "args": ["tenant-id"] + }, + "create_net": { + "func": create_net, + "api_func": api_create_net, + "args": ["tenant-id", "net-name"] + }, + "delete_net": { + "func": delete_net, + "api_func": api_delete_net, + "args": ["tenant-id", "net-id"] + }, + "detail_net": { + "func": detail_net, + "api_func": api_detail_net, + "args": ["tenant-id", "net-id"] + }, + "rename_net": { + "func": rename_net, + "api_func": api_rename_net, + "args": ["tenant-id", "net-id", "new-name"] + }, + "list_ports": { + "func": list_ports, + "api_func": api_list_ports, + "args": ["tenant-id", "net-id"] + }, + "create_port": { + "func": create_port, + "api_func": api_create_port, + "args": ["tenant-id", "net-id"] + }, + "delete_port": { + "func": delete_port, + "api_func": api_delete_port, + "args": ["tenant-id", "net-id", "port-id"] + }, + "detail_port": { + "func": detail_port, + "api_func": api_detail_port, + "args": ["tenant-id", "net-id", "port-id"] + }, + "plug_iface": { + "func": plug_iface, + "args": ["tenant-id", "net-id", "port-id", "iface-id"] + }, + "unplug_iface": { + "func": unplug_iface, + "args": ["tenant-id", "net-id", "port-id"] + }, + "detail_iface": { + "func": detail_iface, + "args": ["tenant-id", "net-id", "port-id"] + }, + "list_iface": { + "func": list_iface, + "args": ["tenant-id", "net-id"] + }, + } + +def help(): + print "\nCommands:" + for k in commands.keys(): + print " %s %s" % (k, + " ".join(["<%s>" % y for y in commands[k]["args"]])) + +def build_args(cmd, cmdargs, arglist): + args = [] + orig_arglist = arglist[:] + try: + for x in cmdargs: + args.append(arglist[0]) + del arglist[0] + except Exception, e: + LOG.error("Not enough arguments for \"%s\" (expected: %d, got: %d)" % ( + cmd, len(cmdargs), len(orig_arglist))) + print "Usage:\n %s %s" % (cmd, + " ".join(["<%s>" % y for y in commands[cmd]["args"]])) + return None + if len(arglist) > 0: + LOG.error("Too many arguments for \"%s\" (expected: %d, got: %d)" % ( + cmd, len(cmdargs), len(orig_arglist))) + print "Usage:\n %s %s" % (cmd, + " ".join(["<%s>" % y for y in commands[cmd]["args"]])) + return None + return args + +if __name__ == "__main__": + usagestr = "Usage: %prog [OPTIONS] [args]" + parser = OptionParser(usage=usagestr) + parser.add_option("-a", "--use-api", dest="use_api", + action="store_true", default=False, help="Use WS API") + parser.add_option("-H", "--host", dest="host", + type="string", default="127.0.0.1", help="ip address of api host") + parser.add_option("-p", "--port", dest="port", + type="int", default=9696, help="api poort") + parser.add_option("-s", "--ssl", dest="ssl", + action="store_true", default=False, help="use ssl") + parser.add_option("-v", "--verbose", dest="verbose", + action="store_true", default=False, help="turn on verbose logging") + + options, args = parser.parse_args() + + if options.verbose: + LOG.basicConfig(level=LOG.DEBUG) + else: + LOG.basicConfig(level=LOG.WARN) + + if len(args) < 1: + parser.print_help() + sys.exit(1) + + cmd = args[0] + if cmd not in commands.keys(): + LOG.error("Unknown command: %s" % cmd) + help() + sys.exit(1) + + args = build_args(cmd, commands[cmd]["args"], args[1:]) + if not args: + sys.exit(1) + LOG.debug("Executing command \"%s\" with args: %s" % (cmd, args)) + if options.use_api: + client = MiniClient(options.host, options.port, options.ssl) + if not commands[cmd].has_key("api_func"): + LOG.error("API version of \"%s\" is not yet implemented" % cmd) + sys.exit(1) + commands[cmd]["api_func"](client, *args) + else: + quantum = QuantumManager() + manager = quantum.get_manager() + commands[cmd]["func"](manager, *args) + sys.exit(0)