Merging dan wendlandt's bugfixes for Bug #800466 and improvements that enable Quantum to seamlessly

run on KVM!


removed:
  quantum/plugins/openvswitch/agent/set_external_ids.sh
added:
  tools/batch_config.py
renamed:
  quantum/plugins/openvswitch/agent/install.sh => quantum/plugins/openvswitch/agent/xenserver_install.sh
modified:
  quantum/cli.py
  quantum/db/api.py
  quantum/plugins/openvswitch/README
  quantum/plugins/openvswitch/agent/ovs_quantum_agent.py
  quantum/plugins/openvswitch/ovs_db.py
  quantum/plugins/openvswitch/ovs_models.py
  quantum/plugins/openvswitch/ovs_quantum_plugin.ini
  quantum/plugins/openvswitch/ovs_quantum_plugin.py
pending merges:
  Dan Wendlandt 2011-06-27 fix pep8 introduced by trunk merge
    Dan Wendlandt 2011-06-27 [merge] merge
    Dan Wendlandt 2011-06-27 more pep8 goodness
    Dan Wendlandt 2011-06-25 refactor batch_config, allow multiple attaches with the empty string
    Dan Wendlandt 2011-06-21 [merge] merge and pep8 cleanup
    Dan Wendlandt 2011-06-21 add example to usage string for batch_config.py
    Dan Wendlandt 2011-06-21 Bug fixes and clean-up, including supporting libvirt
This commit is contained in:
Somik Behera 2011-06-27 16:34:28 -07:00
commit 5d23ba2598
11 changed files with 239 additions and 110 deletions

View File

@ -240,6 +240,7 @@ def api_create_port(client, *args):
def delete_port(manager, *args): def delete_port(manager, *args):
tid, nid, pid = args tid, nid, pid = args
manager.delete_port(tid, nid, pid)
LOG.info("Deleted Virtual Port:%s " \ LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid)) "on Virtual Network:%s" % (pid, nid))
@ -318,7 +319,8 @@ def api_unplug_iface(client, *args):
output = res.read() output = res.read()
LOG.debug(output) LOG.debug(output)
if res.status != 202: if res.status != 202:
LOG.error("Failed to unplug iface from port \"%s\": %s" % (pid, output)) LOG.error("Failed to unplug iface from port \"%s\": %s" % \
(pid, output))
return return
print "Unplugged interface from port:%s on network:%s" % (pid, nid) print "Unplugged interface from port:%s on network:%s" % (pid, nid)

View File

@ -72,7 +72,7 @@ def network_create(tenant_id, name):
net = None net = None
try: try:
net = session.query(models.Network).\ net = session.query(models.Network).\
filter_by(name=name).\ filter_by(tenant_id=tenant_id, name=name).\
one() one()
raise Exception("Network with name \"%s\" already exists" % name) raise Exception("Network with name \"%s\" already exists" % name)
except exc.NoResultFound: except exc.NoResultFound:
@ -104,7 +104,7 @@ def network_rename(net_id, tenant_id, new_name):
session = get_session() session = get_session()
try: try:
res = session.query(models.Network).\ res = session.query(models.Network).\
filter_by(name=new_name).\ filter_by(tenant_id=tenant_id, name=new_name).\
one() one()
except exc.NoResultFound: except exc.NoResultFound:
net = network_get(net_id) net = network_get(net_id)
@ -156,13 +156,14 @@ def port_get(port_id):
def port_set_attachment(port_id, new_interface_id): def port_set_attachment(port_id, new_interface_id):
session = get_session() session = get_session()
ports = None ports = []
try: if new_interface_id != "":
ports = session.query(models.Port).\ try:
filter_by(interface_id=new_interface_id).\ ports = session.query(models.Port).\
all() filter_by(interface_id=new_interface_id).\
except exc.NoResultFound: all()
pass except exc.NoResultFound:
pass
if len(ports) == 0: if len(ports) == 0:
port = port_get(port_id) port = port_get(port_id)
port.interface_id = new_interface_id port.interface_id = new_interface_id

View File

@ -62,20 +62,25 @@ mysql> FLUSH PRIVILEGES;
distribution tarball (see below) and the agent will use the credentials here distribution tarball (see below) and the agent will use the credentials here
to access the database. to access the database.
# -- Agent configuration # -- XenServer Agent configuration
- Create the agent distribution tarball - Create the agent distribution tarball
$ make agent-dist $ make agent-dist
- Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova - Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova
compute node) compute node)
- Unpack the tarball and run install.sh. This will install all of the - Unpack the tarball and run xenserver_install.sh. This will install all of the
necessary pieces into /etc/xapi.d/plugins. It will also spit out the name 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. of the integration bridge that you'll need for your nova configuration.
Make sure to specify this in your nova flagfile as --flat_network_bridge. Make sure to specify this in your nova flagfile as --flat_network_bridge.
- Run the agent [on your hypervisor (dom0)]: - Run the agent [on your hypervisor (dom0)]:
$ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini $ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini
# -- KVM Agent configuration
- Copy ovs_quantum_agent.py and ovs_quantum_plugin.ini to the Linux host and run:
$ python ovs_quantum_agent.py ovs_quantum_plugin.ini
# -- Getting quantum up and running # -- Getting quantum up and running
- Start quantum [on the quantum service host]: - Start quantum [on the quantum service host]:

View File

@ -130,40 +130,40 @@ class OVSBridge:
def get_port_stats(self, port_name): def get_port_stats(self, port_name):
return self.db_get_map("Interface", port_name, "statistics") return self.db_get_map("Interface", port_name, "statistics")
# this is a hack that should go away once nova properly reports bindings
# to quantum. We have this here for now as it lets us work with
# unmodified nova
def xapi_get_port(self, name):
external_ids = self.db_get_map("Interface", name, "external_ids")
if "attached-mac" not in external_ids:
return None
vm_uuid = external_ids.get("xs-vm-uuid", "")
if len(vm_uuid) == 0:
return None
LOG.debug("iface-id not set, got xs-vm-uuid: %s" % vm_uuid)
res = os.popen("xe vm-list uuid=%s params=name-label --minimal" \
% vm_uuid).readline().strip()
if len(res) == 0:
return None
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")
return VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
# returns a VIF object for each VIF port # returns a VIF object for each VIF port
def get_vif_ports(self): def get_vif_ports(self):
edge_ports = [] edge_ports = []
port_names = self.get_port_name_list() port_names = self.get_port_name_list()
for name in port_names: for name in port_names:
external_ids = self.db_get_map("Interface", name, "external_ids") external_ids = self.db_get_map("Interface", name, "external_ids")
if "iface-id" in external_ids and "attached-mac" in external_ids: if "xs-vm-uuid" in external_ids:
ofport = self.db_get_val("Interface", name, "ofport") p = xapi_get_port(name)
p = VifPort(name, ofport, external_ids["iface-id"], if p is not None:
external_ids["attached-mac"], self) edge_ports.append(p)
edge_ports.append(p) elif "iface-id" in external_ids and "attached-mac" in external_ids:
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") ofport = self.db_get_val("Interface", name, "ofport")
p = VifPort(name, ofport, external_ids["iface-id"], p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self) external_ids["attached-mac"], self)
@ -171,13 +171,15 @@ class OVSBridge:
return edge_ports return edge_ports
class OVSNaaSPlugin: class OVSQuantumAgent:
def __init__(self, integ_br): def __init__(self, integ_br):
self.setup_integration_br(integ_br) self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id): def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name, "tag", self.int_br.set_db_attribute("Port", port.port_name, "tag",
str(vlan_id)) str(vlan_id))
self.int_br.delete_flows(match="in_port=%s" % port.ofport)
def port_unbound(self, port, still_exists): def port_unbound(self, port, still_exists):
if still_exists: if still_exists:
@ -186,13 +188,8 @@ class OVSNaaSPlugin:
def setup_integration_br(self, integ_br): def setup_integration_br(self, integ_br):
self.int_br = OVSBridge(integ_br) self.int_br = OVSBridge(integ_br)
self.int_br.remove_all_flows() self.int_br.remove_all_flows()
# drop all traffic on the 'dead vlan' # switch all traffic using L2 learning
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") 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): def daemon_loop(self, conn):
self.local_vlan_map = {} self.local_vlan_map = {}
@ -201,7 +198,7 @@ class OVSNaaSPlugin:
while True: while True:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT * FROM network_bindings") cursor.execute("SELECT * FROM ports")
rows = cursor.fetchall() rows = cursor.fetchall()
cursor.close() cursor.close()
all_bindings = {} all_bindings = {}
@ -226,22 +223,26 @@ class OVSNaaSPlugin:
else: else:
# no binding, put him on the 'dead vlan' # no binding, put him on the 'dead vlan'
self.int_br.set_db_attribute("Port", p.port_name, "tag", self.int_br.set_db_attribute("Port", p.port_name, "tag",
"4095") "4095")
self.int_br.add_flow(priority=2,
match="in_port=%s" % p.ofport, actions="drop")
old_b = old_local_bindings.get(p.vif_id, None) old_b = old_local_bindings.get(p.vif_id, None)
new_b = new_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 != new_b:
if old_b is not None: if old_b is not None:
LOG.info("Removing binding to net-id = %s for %s" LOG.info("Removing binding to net-id = %s for %s"
% (old_b, str(p))) % (old_b, str(p)))
self.port_unbound(p, True) self.port_unbound(p, True)
if new_b is not None: 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 # If we don't have a binding we have to stick it on
# the dead vlan # the dead vlan
vlan_id = vlan_bindings.get(all_bindings[p.vif_id], vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
"4095") "4095")
self.port_bound(p, vlan_id) self.port_bound(p, vlan_id)
LOG.info("Adding binding to net-id = %s " \
"for %s on vlan %s" % (new_b, str(p), vlan_id))
for vif_id in old_vif_ports.keys(): for vif_id in old_vif_ports.keys():
if vif_id not in new_vif_ports: if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id) LOG.info("Port Disappeared: %s" % vif_id)
@ -251,8 +252,6 @@ class OVSNaaSPlugin:
old_vif_ports = new_vif_ports old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings old_local_bindings = new_local_bindings
self.int_br.run_cmd(["bash",
"/etc/xapi.d/plugins/set_external_ids.sh"])
time.sleep(2) time.sleep(2)
if __name__ == "__main__": if __name__ == "__main__":
@ -291,7 +290,7 @@ if __name__ == "__main__":
LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host)) LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host))
conn = MySQLdb.connect(host=db_host, user=db_user, conn = MySQLdb.connect(host=db_host, user=db_user,
passwd=db_pass, db=db_name) passwd=db_pass, db=db_name)
plugin = OVSNaaSPlugin(integ_br) plugin = OVSQuantumAgent(integ_br)
plugin.daemon_loop(conn) plugin.daemon_loop(conn)
finally: finally:
if conn: if conn:

View File

@ -1,15 +0,0 @@
#!/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

@ -56,21 +56,3 @@ def remove_vlan_binding(netid):
except exc.NoResultFound: except exc.NoResultFound:
pass pass
session.flush() 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

@ -26,23 +26,6 @@ from sqlalchemy.orm import relation
from quantum.db.models import BASE 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): class VlanBinding(BASE):
"""Represents a binding of network_id, vlan_id""" """Represents a binding of network_id, vlan_id"""
__tablename__ = 'vlan_bindings' __tablename__ = 'vlan_bindings'

View File

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

View File

@ -200,11 +200,9 @@ class OVSQuantumPlugin(QuantumPluginBase):
def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id): def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
db.port_set_attachment(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): def unplug_interface(self, tenant_id, net_id, port_id):
db.port_set_attachment(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): def get_interface_details(self, tenant_id, net_id, port_id):
res = db.port_get(port_id) res = db.port_get(port_id)

174
tools/batch_config.py Normal file
View File

@ -0,0 +1,174 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
#
# 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: Dan Wendlandt, Nicira Networks, Inc.
import httplib
import logging as LOG
import json
import socket
import sys
import urllib
from quantum.manager import QuantumManager
from optparse import OptionParser
from quantum.common.wsgi import Serializer
from quantum.cli import MiniClient
FORMAT = "json"
CONTENT_TYPE = "application/" + FORMAT
def delete_all_nets(client, tenant_id):
res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT)
resdict = json.loads(res.read())
LOG.debug(resdict)
for n in resdict["networks"]:
nid = n["id"]
res = client.do_request(tenant_id, 'GET',
"/networks/%s/ports.%s" % (nid, FORMAT))
output = res.read()
if res.status != 200:
LOG.error("Failed to list ports: %s" % output)
continue
rd = json.loads(output)
LOG.debug(rd)
for port in rd["ports"]:
pid = port["id"]
data = {'port': {'attachment-id': ''}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, '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))
continue
LOG.info("Unplugged interface from port:%s on network:%s" % (pid,
nid))
res = client.do_request(tenant_id, 'DELETE',
"/networks/%s/ports/%s.%s" % (nid, pid, FORMAT))
output = res.read()
if res.status != 202:
LOG.error("Failed to delete port: %s" % output)
continue
print "Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid)
res = client.do_request(tenant_id, 'DELETE',
"/networks/" + nid + "." + FORMAT)
status = res.status
if status != 202:
Log.error("Failed to delete network: %s" % nid)
output = res.read()
print output
else:
print "Deleted Virtual Network with ID:%s" % nid
def create_net_with_attachments(net_name, iface_ids):
data = {'network': {'network-name': '%s' % net_name}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, 'POST',
"/networks." + FORMAT, body=body)
rd = json.loads(res.read())
LOG.debug(rd)
nid = rd["networks"]["network"]["id"]
print "Created a new Virtual Network %s with ID:%s" % (net_name, nid)
for iface_id in iface_ids:
res = client.do_request(tenant_id, 'POST',
"/networks/%s/ports.%s" % (nid, FORMAT))
output = res.read()
if res.status != 200:
LOG.error("Failed to create port: %s" % output)
continue
rd = json.loads(output)
new_port_id = rd["ports"]["port"]["id"]
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port_id, nid)
data = {'port': {'attachment-id': '%s' % iface_id}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, 'PUT',
"/networks/%s/ports/%s/attachment.%s" %\
(nid, new_port_id, FORMAT), body=body)
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % \
(iface_id, new_port_id, output))
continue
print "Plugged interface \"%s\" to port:%s on network:%s" % \
(iface_id, new_port_id, nid)
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <tenant-id> <config-string> [args]\n" \
"Example config-string: net1=instance-1,instance-2"\
":net2=instance-3,instance-4\n" \
"This string would create two networks: \n" \
"'net1' would have two ports, with iface-ids "\
"instance-1 and instance-2 attached\n" \
"'net2' would have two ports, with iface-ids"\
" instance-3 and instance-4 attached\n"
parser = OptionParser(usage=usagestr)
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")
parser.add_option("-d", "--delete", dest="delete",
action="store_true", default=False, \
help="delete existing tenants networks")
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)
nets = {}
tenant_id = args[0]
if len(args) > 1:
config_str = args[1]
for net_str in config_str.split(":"):
arr = net_str.split("=")
net_name = arr[0]
nets[net_name] = arr[1].split(",")
print "nets: %s" % str(nets)
client = MiniClient(options.host, options.port, options.ssl)
if options.delete:
delete_all_nets(client, tenant_id)
for net_name, iface_ids in nets.items():
create_net_with_attachments(net_name, iface_ids)
sys.exit(0)