Merge changes from quantum-framework-bgh branch

This commit is contained in:
Brad Hall 2011-06-06 16:50:33 -07:00
commit 0c25ae807f
18 changed files with 1641 additions and 127 deletions

View File

@ -51,12 +51,11 @@ class APIRouterV01(wsgi.Router):
mapper.resource('network', 'networks',
controller=networks.Controller(),
path_prefix=uri_prefix)
mapper.resource('port', 'ports',
mapper.resource('port', 'ports',
controller=ports.Controller(),
parent_resource=dict(member_name='network',
collection_name=\
uri_prefix + 'networks'))
mapper.connect("get_resource",
uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}',

View File

@ -29,15 +29,13 @@ class Controller(common.QuantumController):
_port_ops_param_list = [{
'param-name': 'port-state',
'default-value': 'DOWN',
'default-value': 'DOWN',
'required': False},]
_attachment_ops_param_list = [{
'param-name': 'attachment-id',
'required': True},]
_serialization_metadata = {
"application/xml": {
"attributes": {
@ -49,7 +47,7 @@ class Controller(common.QuantumController):
def __init__(self, plugin_conf_file=None):
self._resource_name = 'port'
super(Controller, self).__init__()
def index(self, req, tenant_id, network_id):
""" Returns a list of port ids for a given network """
return self._items(req, tenant_id, network_id, is_detail=False)
@ -64,7 +62,7 @@ class Controller(common.QuantumController):
return dict(ports=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
def show(self, req, tenant_id, network_id, id):
""" Returns port details for given port and network """
try:
@ -77,7 +75,7 @@ class Controller(common.QuantumController):
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
return faults.Fault(faults.PortNotFound(e))
def create(self, req, tenant_id, network_id):
""" Creates a new port for a given network """
@ -87,17 +85,17 @@ class Controller(common.QuantumController):
self._parse_request_params(req, self._port_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
try:
port = self.network_manager.create_port(tenant_id,
network_id,
network_id,
req_params['port-state'])
builder = ports_view.get_view_builder(req)
result = builder.build(port)
return dict(ports=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.StateInvalid as e:
return faults.Fault(faults.RequestedStateInvalid(e))
except exception.StateInvalid as e:
return faults.Fault(faults.RequestedStateInvalid(e))
def update(self, req, tenant_id, network_id, id):
""" Updates the state of a port for a given network """
@ -106,8 +104,8 @@ class Controller(common.QuantumController):
req_params = \
self._parse_request_params(req, self._port_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
return faults.Fault(e)
try:
port = self.network_manager.update_port(tenant_id,network_id, id,
req_params['port-state'])
builder = ports_view.get_view_builder(req)
@ -117,10 +115,9 @@ class Controller(common.QuantumController):
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
except exception.StateInvalid as e:
except exception.StateInvalid as e:
return faults.Fault(faults.RequestedStateInvalid(e))
def delete(self, req, tenant_id, network_id, id):
""" Destroys the port with the given id """
#look for port state in request
@ -131,11 +128,10 @@ class Controller(common.QuantumController):
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
return faults.Fault(faults.PortNotFound(e))
except exception.PortInUse as e:
return faults.Fault(faults.PortInUse(e))
def get_resource(self,req,tenant_id, network_id, id):
try:
result = self.network_manager.get_interface_details(
@ -144,9 +140,9 @@ class Controller(common.QuantumController):
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
return faults.Fault(faults.PortNotFound(e))
#TODO - Complete implementation of these APIs
#TODO - Complete implementation of these APIs
def attach_resource(self,req,tenant_id, network_id, id):
content_type = req.best_match_content_type()
print "Content type:%s" %content_type
@ -164,14 +160,13 @@ class Controller(common.QuantumController):
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
return faults.Fault(faults.PortNotFound(e))
except exception.PortInUse as e:
return faults.Fault(faults.PortInUse(e))
except exception.AlreadyAttached as e:
return faults.Fault(faults.AlreadyAttached(e))
#TODO - Complete implementation of these APIs
#TODO - Complete implementation of these APIs
def detach_resource(self,req,tenant_id, network_id, id):
try:
self.network_manager.unplug_interface(tenant_id,
@ -180,4 +175,4 @@ class Controller(common.QuantumController):
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
return faults.Fault(faults.PortNotFound(e))

View File

@ -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']))

View File

@ -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,417 @@
# 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 <tenant-id>"
print "create_net <tenant-id> <net-name>"
print "delete_net <tenant-id> <net-id>"
print "detail_net <tenant-id> <net-id>"
print "rename_net <tenant-id> <net-id> <new name>"
print "list_ports <tenant-id> <net-id>"
print "create_port <tenant-id> <net-id>"
print "delete_port <tenant-id> <net-id> <port-id>"
print "detail_port <tenant-id> <net-id> <port-id>"
print "plug_iface <tenant-id> <net-id> <port-id> <iface-id>"
print "unplug_iface <tenant-id> <net-id> <port-id>"
print "detail_iface <tenant-id> <net-id> <port-id>"
print "list_iface <tenant-id> <net-id>\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
iface_list = manager.get_network_details(tid, nid)
print "Remote Interfaces on Virtual Network:%s\n" % nid
for iface in iface_list:
print "\tRemote interface:%s" % iface
def api_detail_net(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 "Remote Interfaces on Virtual Network:%s\n" % nid
for port in rd["ports"]:
pid = port["id"]
res = client.do_request(tid, 'GET',
"/networks/%s/ports/%s/attachment.%s" % (nid, pid, FORMAT))
output = res.read()
rd = simplejson.loads(output)
LOG.debug(rd)
remote_iface = rd["attachment"]
print "\tRemote interface:%s" % remote_iface
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
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:
print "\tVirtual Port:%s" % port["port-id"]
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)
def plug_iface(manager, *args):
tid, nid, pid, vid = args
manager.plug_interface(tid, nid, pid, vid)
print "Plugged remote interface:%s " \
"into Virtual Network:%s" % (vid, nid)
def api_plug_iface(client, *args):
tid, nid, pid, vid = args
data = {'port': {'attachment-id': '%s' % vid}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tid, 'PUT',
"/networks/%s/ports/%s/attachment.%s" % (nid, pid, FORMAT), body=body)
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid,
pid, output))
return
print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, nid)
def unplug_iface(manager, *args):
tid, nid, pid = args
manager.unplug_interface(tid, nid, pid)
print "UnPlugged remote interface " \
"from Virtual Port:%s Virtual Network:%s" % (pid, nid)
def api_unplug_iface(client, *args):
tid, nid, pid = args
data = {'port': {'attachment-id': ''}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tid, 'DELETE',
"/networks/%s/ports/%s/attachment.%s" % (nid, pid, FORMAT), body=body)
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid,
pid, output))
return
print "Unplugged interface from port:%s on network:%s" % (pid, nid)
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,
"api_func": api_plug_iface,
"args": ["tenant-id", "net-id", "port-id", "iface-id"]
},
"unplug_iface": {
"func": unplug_iface,
"api_func": api_unplug_iface,
"args": ["tenant-id", "net-id", "port-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] <command> [args]"
parser = OptionParser(usage=usagestr)
parser.add_option("-l", "--load-plugin", dest="load_plugin",
action="store_true", default=False,
help="Load plugin directly instead of using 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()
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 not options.load_plugin:
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)

17
quantum/db/__init__.py Normal file
View File

@ -0,0 +1,17 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.

173
quantum/db/api.py Normal file
View File

@ -0,0 +1,173 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, exc
import models
_ENGINE = None
_MAKER = None
BASE = models.BASE
def configure_db(options):
"""
Establish the database, create an engine if needed, and
register the models.
:param options: Mapping of configuration options
"""
global _ENGINE
if not _ENGINE:
_ENGINE = create_engine(options['sql_connection'],
echo=False,
echo_pool=True,
pool_recycle=3600)
register_models()
def get_session(autocommit=True, expire_on_commit=False):
"""Helper method to grab session"""
global _MAKER, _ENGINE
if not _MAKER:
assert _ENGINE
_MAKER = sessionmaker(bind=_ENGINE,
autocommit=autocommit,
expire_on_commit=expire_on_commit)
return _MAKER()
def register_models():
"""Register Models and create properties"""
global _ENGINE
assert _ENGINE
BASE.metadata.create_all(_ENGINE)
def unregister_models():
"""Unregister Models, useful clearing out data before testing"""
global _ENGINE
assert _ENGINE
BASE.metadata.drop_all(_ENGINE)
def network_create(tenant_id, name):
session = get_session()
net = None
try:
net = session.query(models.Network).\
filter_by(name=name).\
one()
raise Exception("Network with name \"%s\" already exists" % name)
except exc.NoResultFound:
with session.begin():
net = models.Network(tenant_id, name)
session.add(net)
session.flush()
return net
def network_list(tenant_id):
session = get_session()
return session.query(models.Network).\
filter_by(tenant_id=tenant_id).\
all()
def network_get(net_id):
session = get_session()
try:
return session.query(models.Network).\
filter_by(uuid=net_id).\
one()
except exc.NoResultFound:
raise Exception("No net found with id = %s" % net_id)
def network_rename(net_id, tenant_id, new_name):
session = get_session()
try:
res = session.query(models.Network).\
filter_by(name=new_name).\
one()
except exc.NoResultFound:
net = network_get(net_id)
net.name = new_name
session.merge(net)
session.flush()
return net
raise Exception("A network with name \"%s\" already exists" % new_name)
def network_destroy(net_id):
session = get_session()
try:
net = session.query(models.Network).\
filter_by(uuid=net_id).\
one()
session.delete(net)
session.flush()
return net
except exc.NoResultFound:
raise Exception("No network found with id = %s" % net_id)
def port_create(net_id):
session = get_session()
with session.begin():
port = models.Port(net_id)
session.add(port)
session.flush()
return port
def port_list(net_id):
session = get_session()
return session.query(models.Port).\
filter_by(network_id=net_id).\
all()
def port_get(port_id):
session = get_session()
try:
return session.query(models.Port).\
filter_by(uuid=port_id).\
one()
except exc.NoResultFound:
raise Exception("No port found with id = %s " % port_id)
def port_set_attachment(port_id, new_interface_id):
session = get_session()
ports = None
try:
ports = session.query(models.Port).\
filter_by(interface_id=new_interface_id).\
all()
except exc.NoResultFound:
pass
if len(ports) == 0:
port = port_get(port_id)
port.interface_id = new_interface_id
session.merge(port)
session.flush()
return port
else:
raise Exception("Port with attachment \"%s\" already exists" % (new_interface_id))
def port_destroy(port_id):
session = get_session()
try:
port = session.query(models.Port).\
filter_by(uuid=port_id).\
one()
session.delete(port)
session.flush()
return port
except exc.NoResultFound:
raise Exception("No port found with id = %s " % port_id)

59
quantum/db/models.py Normal file
View File

@ -0,0 +1,59 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
import uuid
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation
BASE = declarative_base()
class Port(BASE):
"""Represents a port on a quantum network"""
__tablename__ = 'ports'
uuid = Column(String(255), primary_key=True)
network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False)
interface_id = Column(String(255))
def __init__(self, network_id):
self.uuid = uuid.uuid4()
self.network_id = network_id
def __repr__(self):
return "<Port(%s,%s,%s)>" % (self.uuid, self.network_id, self.interface_id)
class Network(BASE):
"""Represents a quantum network"""
__tablename__ = 'networks'
uuid = Column(String(255), primary_key=True)
tenant_id = Column(String(255), nullable=False)
name = Column(String(255))
ports = relation(Port, order_by=Port.uuid, backref="network")
def __init__(self, tenant_id, name):
self.uuid = uuid.uuid4()
self.tenant_id = tenant_id
self.name = name
def __repr__(self):
return "<Network(%s,%s,%s)>" % \
(self.uuid,self.name,self.tenant_id)

View File

@ -26,17 +26,26 @@ The caller should make sure that QuantumManager is a singleton.
import gettext
gettext.install('quantum', unicode=1)
import os
from common import utils
from quantum_plugin_base import QuantumPluginBase
CONFIG_FILE = "plugins.ini"
def find_config(basepath):
for root, dirs, files in os.walk(basepath):
if CONFIG_FILE in files:
return os.path.join(root, CONFIG_FILE)
return None
class QuantumManager(object):
def __init__(self,config=CONFIG_FILE):
self.configuration_file = CONFIG_FILE
plugin_location = utils.getPluginFromConfig(CONFIG_FILE)
def __init__(self, config=None):
if config == None:
self.configuration_file = find_config(os.path.abspath(os.path.dirname(__file__)))
else:
self.configuration_file = config
plugin_location = utils.getPluginFromConfig(self.configuration_file)
print "PLUGIN LOCATION:%s" % plugin_location
plugin_klass = utils.import_class(plugin_location)
if not issubclass(plugin_klass, QuantumPluginBase):
@ -50,15 +59,3 @@ class QuantumManager(object):
def get_manager(self):
return self.plugin
# TODO(somik): rmove the main class
# Added for temporary testing purposes
def main():
manager = QuantumManager()
myManager = manager.get_manager()
myManager.get_all_networks("tesst")
#print("is a plugin")
# Standard boilerplate to call the main() function.
if __name__ == '__main__':
main()

View File

@ -0,0 +1,30 @@
QUANTUM_PATH=../../../
# TODO(bgh): DIST_DIR and target for plugin
AGENT_DIST_DIR=ovs_quantum_agent
AGENT_DIST_TARBALL=ovs_quantum_agent.tgz
agent-dist: distclean
mkdir $(AGENT_DIST_DIR)
cp agent/*.py $(AGENT_DIST_DIR)
cp agent/*.sh $(AGENT_DIST_DIR)
cp README $(AGENT_DIST_DIR)
cp ovs_quantum_plugin.ini $(AGENT_DIST_DIR)
tar -zcvf $(AGENT_DIST_TARBALL) $(AGENT_DIST_DIR)/
@echo "Agent tarball created: $(AGENT_DIST_TARBALL)"
@echo "See README for installation details"
all:
clean:
$(find . -name *.pyc | xargs rm)
distclean:
-rm -rf $(AGENT_DIST_DIR)
-rm -f $(AGENT_DIST_TARBALL)
check:
PYTHONPATH=$(QUANTUM_PATH):. python ovs_quantum_plugin.py
PHONY: agent-dist check clean distclean

View File

@ -0,0 +1,106 @@
# -- Background
The quantum openvswitch plugin is a simple plugin that allows you to manage
connectivity between VMs on hypervisors running openvswitch.
The quantum openvswitch plugin consists of two components:
1) The plugin itself: The plugin uses a database backend (mysql for now) to
store configuration and mappings that are used by the agent.
2) An agent which runs on the hypervisor (dom0) and communicates with
openvswitch. The agent gathers the configuration and mappings from the
mysql database running on the quantum host.
The sections below describe how to configure and run the quantum service with
the openvswitch plugin.
# -- Nova configuration
- Make sure to set up nova using flat networking. Also, make sure that the
integration bridge (see below under agent configuration) matches the
flat_network_bridge specified in your nova flag file. Here are the relevant
entries from my nova flag file.
--network_manager=nova.network.manager.FlatManager
--flat_network_bridge=xapi1
# -- Quantum configuration
Make the openvswitch plugin the current quantum plugin
- edit ../../plugins.ini and change the provider line to be:
provider = quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPlugin
# -- Database config. The OVS quantum service requires access to a mysql
# database in order to store configuration and mappings that will be used by
# the agent. Here is how to set up the database on the host that you will be
# running the quantum service on.
MySQL should be installed on the host, and all plugins and clients must be
configured with access to the database.
To prep mysql, run:
$ mysql -u root -p -e "create database ovs_quantum"
Make sure any xenserver running the ovs quantum agent will be able to communicate with the host running the quantum service:
//log in to mysql service
$ mysql -u root -p
//grant access to user-remote host combination
mysql> GRANT USAGE ON *.* to root@'yourremotehost' IDENTIFIED BY 'newpassword';
//force update of authorization changes
mysql> FLUSH PRIVILEGES;
# -- Plugin configuration.
- Edit the configuration file (ovs_quantum_plugin.ini). Make sure it matches
your mysql configuration. This file must be updated with the addresses and
credentials to access the database. This file will be included in the agent
distribution tarball (see below) and the agent will use the credentials here
to access the database.
# -- Agent configuration
- Create the agent distribution tarball
$ make agent-dist
- Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova
compute node)
- Unpack the tarball and run install.sh. This will install all of the
necessary pieces into /etc/xapi.d/plugins. It will also spit out the name
of the integration bridge that you'll need for your nova configuration.
- Run the agent [on your hypervisor (dom0)]:
$ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini
# -- Getting quantum up and running
- Start quantum [on the quantum service host]:
~/src/quantum-framework$ PYTHONPATH=.:$PYTHONPATH python bin/quantum etc/quantum.conf
- Run ovs_quantum_plugin.py via the quantum plugin framework cli [on the
quantum service host]
~/src/quantum-framework$ PYTHONPATH=.:$PYTHONPATH python quantum/cli.py
This will show help all of the available commands.
An example session looks like this:
$ export TENANT=t1
$ PYTHONPATH=. python quantum/cli.py -v create_net $TENANT network1
Created a new Virtual Network with ID:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export NETWORK=e754e7c0-a8eb-40e5-861a-b182d30c3441
$ PYTHONPATH=. python quantum/cli.py -v create_port $TENANT $NETWORK
Created Virtual Port:5a1e121b-ccc8-471d-9445-24f15f9f854c on Virtual Network:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export PORT=5a1e121b-ccc8-471d-9445-24f15f9f854c
$ PYTHONPATH=. python quantum/cli.py -v plug_iface $TENANT $NETWORK $PORT ubuntu1-eth1
Plugged interface "ubuntu1-eth1" to port:5a1e121b-ccc8-471d-9445-24f15f9f854c on network:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ PYTHONPATH=. python quantum/cli.py -v plug_iface $TENANT $NETWORK $PORT ubuntu2-eth1
Plugged interface "ubuntu2-eth1" to port:5a1e121b-ccc8-471d-9445-24f15f9f854c on network:e754e7c0-a8eb-40e5-861a-b182d30c3441
Now you should have connectivity between ubuntu1-eth1 and ubuntu2-eth1..
# -- Other items
- To get a listing of the vif names that the ovs quantum service will expect
them in, issue the following command on the hypervisor (dom0):
$ for vif in `xe vif-list params=uuid --minimal | sed s/,/" "/g`; do echo $(xe vif-list params=vm-name-label uuid=${vif} --minimal)-eth$(xe vif-list params=device uuid=${vif} --minimal); done

View File

View File

@ -0,0 +1,38 @@
#!/bin/bash
CONF_FILE=/etc/xapi.d/plugins/ovs_quantum_plugin.ini
if [ ! -d /etc/xapi.d/plugins ]; then
echo "Am I on a xenserver? I can't find the plugins directory!"
exit 1
fi
# Make sure we have mysql-python
rpm -qa | grep MYyQL-python >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "MySQL-python not found"
echo "Please enable the centos repositories and install mysql-python:"
echo "yum --enablerepo=base -y install MySQL-python"
exit 1
fi
cp ovs_quantum_agent.py /etc/xapi.d/plugins
cp ovs_quantum_plugin.ini /etc/xapi.d/plugins
cp set_external_ids.sh /etc/xapi.d/plugins
xe network-list name-label="integration-bridge" | grep xapi >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "No integration bridge found. Creating."
xe network-create name-label="integration-bridge"
fi
BR=$(xe network-list name-label="integration-bridge" | grep "bridge.*:" | awk '{print $4}')
CONF_BR=$(grep integration-bridge ${CONF_FILE} | cut -d= -f2)
if [ "X$BR" != "X$CONF_BR" ]; then
echo "Integration bridge doesn't match configuration file; fixing."
sed -i -e "s/^integration-bridge =.*$/integration-bridge = ${BR}/g" $CONF_FILE
fi
echo "Using integration bridge: $BR (make sure this is set in the nova configuration)"
echo "Make sure to edit: $CONF_FILE"

View File

@ -0,0 +1,290 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
import ConfigParser
import logging as LOG
import MySQLdb
import os
import sys
import time
from optparse import OptionParser
from subprocess import *
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
# attributes set).
class VifPort:
def __init__(self, port_name, ofport, vif_id, vif_mac, switch):
self.port_name = port_name
self.ofport = ofport
self.vif_id = vif_id
self.vif_mac = vif_mac
self.switch = switch
def __str__(self):
return "iface-id=" + self.vif_id + ", vif_mac=" + \
self.vif_mac + ", port_name=" + self.port_name + \
", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name
class OVSBridge:
def __init__(self, br_name):
self.br_name = br_name
def run_cmd(self, args):
# LOG.debug("## running command: " + " ".join(args))
return Popen(args, stdout=PIPE).communicate()[0]
def run_vsctl(self, args):
full_args = ["ovs-vsctl" ] + args
return self.run_cmd(full_args)
def reset_bridge(self):
self.run_vsctl([ "--" , "--if-exists", "del-br", self.br_name])
self.run_vsctl(["add-br", self.br_name])
def delete_port(self, port_name):
self.run_vsctl([ "--" , "--if-exists", "del-port", self.br_name,
port_name])
def set_db_attribute(self, table_name, record, column, value):
args = [ "set", table_name, record, "%s=%s" % (column,value) ]
self.run_vsctl(args)
def clear_db_attribute(self, table_name,record, column):
args = [ "clear", table_name, record, column ]
self.run_vsctl(args)
def run_ofctl(self, cmd, args):
full_args = ["ovs-ofctl", cmd, self.br_name ] + args
return self.run_cmd(full_args)
def remove_all_flows(self):
self.run_ofctl("del-flows", [])
def get_port_ofport(self, port_name):
return self.db_get_val("Interface", port_name, "ofport")
def add_flow(self,**dict):
if "actions" not in dict:
raise Exception("must specify one or more actions")
if "priority" not in dict:
dict["priority"] = "0"
flow_str = "priority=%s" % dict["priority"]
if "match" in dict:
flow_str += "," + dict["match"]
flow_str += ",actions=%s" % (dict["actions"])
self.run_ofctl("add-flow", [ flow_str ] )
def delete_flows(self,**dict):
all_args = []
if "priority" in dict:
all_args.append("priority=%s" % dict["priority"])
if "match" in dict:
all_args.append(dict["match"])
if "actions" in dict:
all_args.append("actions=%s" % (dict["actions"]))
flow_str = ",".join(all_args)
self.run_ofctl("del-flows", [ flow_str ] )
def db_get_map(self, table, record, column):
str = self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
return self.db_str_to_map(str)
def db_get_val(self, table, record, column):
return self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
def db_str_to_map(self, full_str):
list = full_str.strip("{}").split(", ")
ret = {}
for e in list:
if e.find("=") == -1:
continue
arr = e.split("=")
ret[arr[0]] = arr[1].strip("\"")
return ret
def get_port_name_list(self):
res = self.run_vsctl([ "list-ports", self.br_name])
return res.split("\n")[0:-1]
def get_port_stats(self, port_name):
return self.db_get_map("Interface", port_name, "statistics")
# returns a VIF object for each VIF port
def get_vif_ports(self):
edge_ports = []
port_names = self.get_port_name_list()
for name in port_names:
external_ids = self.db_get_map("Interface",name,"external_ids")
if "iface-id" in external_ids and "attached-mac" in external_ids:
ofport = self.db_get_val("Interface",name,"ofport")
p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
edge_ports.append(p)
else:
# iface-id might not be set. See if we can figure it out and
# set it here.
external_ids = self.db_get_map("Interface",name,"external_ids")
if "attached-mac" not in external_ids:
continue
vif_uuid = external_ids.get("xs-vif-uuid", "")
if len(vif_uuid) == 0:
continue
LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid)
res = os.popen("xe vif-param-get param-name=other-config uuid=%s | grep nicira-iface-id | awk '{print $2}'" % vif_uuid).readline()
res = res.strip()
if len(res) == 0:
continue
external_ids["iface-id"] = res
LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res))
self.set_db_attribute("Interface", name,
"external-ids:iface-id", res)
ofport = self.db_get_val("Interface",name,"ofport")
p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
edge_ports.append(p)
return edge_ports
class OVSNaaSPlugin:
def __init__(self, integ_br):
self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name,"tag",
str(vlan_id))
def port_unbound(self, port, still_exists):
if still_exists:
self.int_br.clear_db_attribute("Port", port.port_name,"tag")
def setup_integration_br(self, integ_br):
self.int_br = OVSBridge(integ_br)
self.int_br.remove_all_flows()
# drop all traffic on the 'dead vlan'
self.int_br.add_flow(priority=2, match="dl_vlan=4095", actions="drop")
# switch all other traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
# FIXME send broadcast everywhere, regardless of tenant
#int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff", actions="normal")
def daemon_loop(self, conn):
self.local_vlan_map = {}
old_local_bindings = {}
old_vif_ports = {}
while True:
cursor = conn.cursor()
cursor.execute("SELECT * FROM network_bindings")
rows = cursor.fetchall()
cursor.close()
all_bindings = {}
for r in rows:
all_bindings[r[2]] = r[1]
cursor = conn.cursor()
cursor.execute("SELECT * FROM vlan_bindings")
rows = cursor.fetchall()
cursor.close()
vlan_bindings = {}
for r in rows:
vlan_bindings[r[1]] = r[0]
new_vif_ports = {}
new_local_bindings = {}
vif_ports = self.int_br.get_vif_ports()
for p in vif_ports:
new_vif_ports[p.vif_id] = p
if p.vif_id in all_bindings:
new_local_bindings[p.vif_id] = all_bindings[p.vif_id]
else:
# no binding, put him on the 'dead vlan'
self.int_br.set_db_attribute("Port", p.port_name, "tag",
"4095")
old_b = old_local_bindings.get(p.vif_id,None)
new_b = new_local_bindings.get(p.vif_id,None)
if old_b != new_b:
if old_b is not None:
LOG.info("Removing binding to net-id = %s for %s"
% (old_b, str(p)))
self.port_unbound(p, True)
if new_b is not None:
LOG.info("Adding binding to net-id = %s for %s" \
% (new_b, str(p)))
# If we don't have a binding we have to stick it on
# the dead vlan
vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
"4095")
self.port_bound(p, vlan_id)
for vif_id in old_vif_ports.keys():
if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id)
if vif_id in old_local_bindings:
old_b = old_local_bindings[vif_id]
self.port_unbound(old_vif_ports[vif_id], False)
old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings
self.int_br.run_cmd(["bash",
"/etc/xapi.d/plugins/set_external_ids.sh"])
time.sleep(2)
if __name__ == "__main__":
usagestr = "%prog [OPTIONS] <config file>"
parser = OptionParser(usage=usagestr)
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)
config_file = args[0]
config = ConfigParser.ConfigParser()
try:
config.read(config_file)
except Exception, e:
LOG.error("Unable to parse config file \"%s\": %s" % (config_file,
str(e)))
integ_br = config.get("OVS", "integration-bridge")
db_name = config.get("DATABASE", "name")
db_user = config.get("DATABASE", "user")
db_pass = config.get("DATABASE", "pass")
db_host = config.get("DATABASE", "host")
conn = None
try:
LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host))
conn = MySQLdb.connect(host=db_host, user=db_user,
passwd=db_pass, db=db_name)
plugin = OVSNaaSPlugin(integ_br)
plugin.daemon_loop(conn)
finally:
if conn:
conn.close()
sys.exit(0)

View File

@ -0,0 +1,15 @@
#!/bin/sh
VIFLIST=`xe vif-list params=uuid --minimal | sed s/,/" "/g`
for VIF_UUID in $VIFLIST; do
DEVICE_NUM=`xe vif-list params=device uuid=$VIF_UUID --minimal`
VM_NAME=`xe vif-list params=vm-name-label uuid=$VIF_UUID --minimal`
NAME="$VM_NAME-eth$DEVICE_NUM"
echo "Vif: $VIF_UUID is '$NAME'"
xe vif-param-set uuid=$VIF_UUID other-config:nicira-iface-id="$NAME"
done
ps auxw | grep -v grep | grep ovs-xapi-sync > /dev/null 2>&1
if [ $? -eq 0 ]; then
killall -HUP ovs-xapi-sync
fi

View File

@ -0,0 +1,72 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
from sqlalchemy.orm import exc
import quantum.db.api as db
import quantum.db.models as models
import ovs_models
def get_vlans():
session = db.get_session()
try:
bindings = session.query(ovs_models.VlanBinding).\
all()
except exc.NoResultFound:
return []
res = []
for x in bindings:
res.append((x.vlan_id, x.network_id))
return res
def add_vlan_binding(vlanid, netid):
session = db.get_session()
binding = ovs_models.VlanBinding(vlanid, netid)
session.add(binding)
session.flush()
return binding.vlan_id
def remove_vlan_binding(netid):
session = db.get_session()
try:
binding = session.query(ovs_models.VlanBinding).\
filter_by(network_id=netid).\
one()
session.delete(binding)
except exc.NoResultFound:
pass
session.flush()
def update_network_binding(netid, ifaceid):
session = db.get_session()
# Add to or delete from the bindings table
if ifaceid == None:
try:
binding = session.query(ovs_models.NetworkBinding).\
filter_by(network_id=netid).\
one()
session.delete(binding)
except exc.NoResultFound:
raise Exception("No binding found with network_id = %s" % netid)
else:
binding = ovs_models.NetworkBinding(netid, ifaceid)
session.add(binding)
session.flush()

View File

@ -0,0 +1,58 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
import uuid
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation
from quantum.db.models import BASE
class NetworkBinding(BASE):
"""Represents a binding of network_id, vif_id"""
__tablename__ = 'network_bindings'
id = Column(Integer, primary_key=True, autoincrement=True)
network_id = Column(String(255))
vif_id = Column(String(255))
def __init__(self, network_id, vif_id):
self.network_id = network_id
self.vif_id = vif_id
def __repr__(self):
return "<NetworkBinding(%s,%s)>" % \
(self.network_id, self.vif_id)
class VlanBinding(BASE):
"""Represents a binding of network_id, vlan_id"""
__tablename__ = 'vlan_bindings'
vlan_id = Column(Integer, primary_key=True)
network_id = Column(String(255))
def __init__(self, vlan_id, network_id):
self.network_id = network_id
self.vlan_id = vlan_id
def __repr__(self):
return "<VlanBinding(%s,%s)>" % \
(self.vlan_id, self.network_id)

View File

@ -0,0 +1,9 @@
[DATABASE]
name = ovs_naas
user = root
pass = foobar
host = 127.0.0.1
port = 3306
[OVS]
integration-bridge = xapi1

View File

@ -0,0 +1,334 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
# @author: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
import ConfigParser
import logging as LOG
import os
import sys
import unittest
from quantum.quantum_plugin_base import QuantumPluginBase
from optparse import OptionParser
import quantum.db.api as db
import ovs_db
CONF_FILE="ovs_quantum_plugin.ini"
LOG.basicConfig(level=LOG.WARN)
LOG.getLogger("ovs_quantum_plugin")
def find_config(basepath):
for root, dirs, files in os.walk(basepath):
if CONF_FILE in files:
return os.path.join(root, CONF_FILE)
return None
class VlanMap(object):
vlans = {}
def __init__(self):
for x in xrange(2, 4094):
self.vlans[x] = None
def set(self, vlan_id, network_id):
self.vlans[vlan_id] = network_id
def acquire(self, network_id):
for x in xrange(2, 4094):
if self.vlans[x] == None:
self.vlans[x] = network_id
# LOG.debug("VlanMap::acquire %s -> %s" % (x, network_id))
return x
raise Exception("No free vlans..")
def get(self, vlan_id):
return self.vlans[vlan_id]
def release(self, network_id):
for x in self.vlans.keys():
if self.vlans[x] == network_id:
self.vlans[x] = None
# LOG.debug("VlanMap::release %s" % (x))
return
LOG.error("No vlan found with network \"%s\"" % network_id)
class OVSQuantumPlugin(QuantumPluginBase):
def __init__(self, configfile=None):
config = ConfigParser.ConfigParser()
if configfile == None:
if os.path.exists(CONF_FILE):
configfile = CONF_FILE
else:
configfile = find_config(os.path.abspath(os.path.dirname(__file__)))
if configfile == None:
raise Exception("Configuration file \"%s\" doesn't exist" %
(configfile))
LOG.debug("Using configuration file: %s" % configfile)
config.read(configfile)
LOG.debug("Config: %s" % config)
DB_NAME = config.get("DATABASE", "name")
DB_USER = config.get("DATABASE", "user")
DB_PASS = config.get("DATABASE", "pass")
DB_HOST = config.get("DATABASE", "host")
options = {"sql_connection": "mysql://%s:%s@%s/%s" % (DB_USER,
DB_PASS, DB_HOST, DB_NAME)}
db.configure_db(options)
self.vmap = VlanMap()
# Populate the map with anything that is already present in the
# database
vlans = ovs_db.get_vlans()
for x in vlans:
vlan_id, network_id = x
# LOG.debug("Adding already populated vlan %s -> %s" % (vlan_id, network_id))
self.vmap.set(vlan_id, network_id)
def get_all_networks(self, tenant_id):
nets = []
for x in db.network_list(tenant_id):
LOG.debug("Adding network: %s" % x.uuid)
d = {}
d["net-id"] = str(x.uuid)
d["net-name"] = x.name
nets.append(d)
return nets
def create_network(self, tenant_id, net_name):
d = {}
try:
res = db.network_create(tenant_id, net_name)
LOG.debug("Created newtork: %s" % res)
except Exception, e:
LOG.error("Error: %s" % str(e))
return d
d["net-id"] = str(res.uuid)
d["net-name"] = res.name
vlan_id = self.vmap.acquire(str(res.uuid))
ovs_db.add_vlan_binding(vlan_id, str(res.uuid))
return d
def delete_network(self, tenant_id, net_id):
net = db.network_destroy(net_id)
d = {}
d["net-id"] = str(net.uuid)
ovs_db.remove_vlan_binding(net_id)
self.vmap.release(net_id)
return d
def get_network_details(self, tenant_id, net_id):
ports = db.port_list(net_id)
ifaces = []
for p in ports:
ifaces.append(p.interface_id)
return ifaces
def rename_network(self, tenant_id, net_id, new_name):
try:
net = db.network_rename(net_id, tenant_id, new_name)
except Exception, e:
raise Exception("Failed to rename network: %s" % str(e))
d = {}
d["net-id"] = str(net.uuid)
d["net-name"] = net.name
return d
def get_all_ports(self, tenant_id, net_id):
ids = []
ports = db.port_list(net_id)
for x in ports:
LOG.debug("Appending port: %s" % x.uuid)
d = {}
d["port-id"] = str(x.uuid)
ids.append(d)
return ids
def create_port(self, tenant_id, net_id, port_state=None):
LOG.debug("Creating port with network_id: %s" % net_id)
port = db.port_create(net_id)
d = {}
d["port-id"] = str(port.uuid)
LOG.debug("-> %s" % (port.uuid))
return d
def delete_port(self, tenant_id, net_id, port_id):
try:
port = db.port_destroy(port_id)
except Exception, e:
raise Exception("Failed to delete port: %s" % str(e))
d = {}
d["port-id"] = str(port.uuid)
return d
def update_port(self, tenant_id, net_id, port_id, port_state):
"""
Updates the state of a port on the specified Virtual Network.
"""
LOG.debug("update_port() called\n")
port = db.port_get(port_id)
port['port-state'] = port_state
return port
def get_port_details(self, tenant_id, net_id, port_id):
port = db.port_get(port_id)
rv = {"port-id": port.uuid, "attachment": port.interface_id,
"net-id": port.network_id, "port-state": "UP"}
return rv
def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
db.port_set_attachment(port_id, remote_iface_id)
ovs_db.update_network_binding(net_id, remote_iface_id)
def unplug_interface(self, tenant_id, net_id, port_id):
db.port_set_attachment(port_id, "")
ovs_db.update_network_binding(net_id, None)
def get_interface_details(self, tenant_id, net_id, port_id):
res = db.port_get(port_id)
return res.interface_id
class VlanMapTest(unittest.TestCase):
def setUp(self):
self.vmap = VlanMap()
def tearDown(self):
pass
def testAddVlan(self):
vlan_id = self.vmap.acquire("foobar")
self.assertTrue(vlan_id == 2)
def testReleaseVlan(self):
vlan_id = self.vmap.acquire("foobar")
self.vmap.release("foobar")
self.assertTrue(self.vmap.get(vlan_id) == None)
# TODO(bgh): Make the tests use a sqlite database instead of mysql
class OVSPluginTest(unittest.TestCase):
def setUp(self):
self.quantum = OVSQuantumPlugin()
self.tenant_id = "testtenant"
def testCreateNetwork(self):
net1 = self.quantum.create_network(self.tenant_id, "plugin_test1")
self.assertTrue(net1["net-name"] == "plugin_test1")
def testGetNetworks(self):
net1 = self.quantum.create_network(self.tenant_id, "plugin_test1")
net2 = self.quantum.create_network(self.tenant_id, "plugin_test2")
nets = self.quantum.get_all_networks(self.tenant_id)
count = 0
for x in nets:
if "plugin_test" in x["net-name"]:
count += 1
self.assertTrue(count == 2)
def testDeleteNetwork(self):
net = self.quantum.create_network(self.tenant_id, "plugin_test1")
self.quantum.delete_network(self.tenant_id, net["net-id"])
nets = self.quantum.get_all_networks(self.tenant_id)
count = 0
for x in nets:
if "plugin_test" in x["net-name"]:
count += 1
self.assertTrue(count == 0)
def testRenameNetwork(self):
net = self.quantum.create_network(self.tenant_id, "plugin_test1")
net = self.quantum.rename_network(self.tenant_id, net["net-id"],
"plugin_test_renamed")
self.assertTrue(net["net-name"] == "plugin_test_renamed")
def testCreatePort(self):
net1 = self.quantum.create_network(self.tenant_id, "plugin_test1")
port = self.quantum.create_port(self.tenant_id, net1["net-id"])
ports = self.quantum.get_all_ports(self.tenant_id, net1["net-id"])
count = 0
for p in ports:
count += 1
self.assertTrue(count == 1)
def testDeletePort(self):
net1 = self.quantum.create_network(self.tenant_id, "plugin_test1")
port = self.quantum.create_port(self.tenant_id, net1["net-id"])
ports = self.quantum.get_all_ports(self.tenant_id, net1["net-id"])
count = 0
for p in ports:
count += 1
self.assertTrue(count == 1)
for p in ports:
self.quantum.delete_port(self.tenant_id, id, p["port-id"])
ports = self.quantum.get_all_ports(self.tenant_id, net1["net-id"])
count = 0
for p in ports:
count += 1
self.assertTrue(count == 0)
def testGetPorts(self):
pass
def testPlugInterface(self):
net1 = self.quantum.create_network(self.tenant_id, "plugin_test1")
port = self.quantum.create_port(self.tenant_id, net1["net-id"])
self.quantum.plug_interface(self.tenant_id, net1["net-id"],
port["port-id"], "vif1.1")
port = self.quantum.get_port_details(self.tenant_id, net1["net-id"],
port["port-id"])
self.assertTrue(port["attachment"] == "vif1.1")
def testUnPlugInterface(self):
net1 = self.quantum.create_network(self.tenant_id, "plugin_test1")
port = self.quantum.create_port(self.tenant_id, net1["net-id"])
self.quantum.plug_interface(self.tenant_id, net1["net-id"],
port["port-id"], "vif1.1")
port = self.quantum.get_port_details(self.tenant_id, net1["net-id"],
port["port-id"])
self.assertTrue(port["attachment"] == "vif1.1")
self.quantum.unplug_interface(self.tenant_id, net1["net-id"],
port["port-id"])
port = self.quantum.get_port_details(self.tenant_id, net1["net-id"],
port["port-id"])
self.assertTrue(port["attachment"] == "")
def tearDown(self):
networks = self.quantum.get_all_networks(self.tenant_id)
# Clean up any test networks lying around
for net in networks:
id = net["net-id"]
name = net["net-name"]
if "plugin_test" in name:
# Clean up any test ports lying around
ports = self.quantum.get_all_ports(self.tenant_id, id)
for p in ports:
self.quantum.delete_port(self.tenant_id, id, p["port-id"])
self.quantum.delete_network(self.tenant_id, id)
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr)
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)
# Make sqlalchemy quieter
LOG.getLogger('sqlalchemy.engine').setLevel(LOG.WARN)
# Run the tests
suite = unittest.TestLoader().loadTestsFromTestCase(OVSPluginTest)
unittest.TextTestRunner(verbosity=2).run(suite)
suite = unittest.TestLoader().loadTestsFromTestCase(VlanMapTest)
unittest.TextTestRunner(verbosity=2).run(suite)