fix issues with Nova security groups and Quantum

bug #1039400

- make quantumv2/api.py fetch actual DHCP server address, which
is needed by firewall layer (otherwise, the gateway IP is
incorrectly used and all DHCP traffic is dropped).
- add missing call from quantumv2/api.py to the security
groups API when a VM is allocated/deallocated.

- Add a vif-driver that is a hybrid of the existing Open vswitch +
linux bridge drivers, which allows OVS quantum plugins to
be compatible with iptables based filtering, in particular, nova
security groups.

- Also clean-up some docstrings in virt/libvirt/vif.py

Change-Id: I7cf5cf09583202a12785b616d18db3ee4bbffee0
This commit is contained in:
Dan Wendlandt 2012-08-30 22:21:51 -07:00
parent 0749ffc3be
commit 5a470f89b6
5 changed files with 184 additions and 29 deletions

View File

@ -997,6 +997,26 @@ def _ip_bridge_cmd(action, params, device):
return cmd
def _create_veth_pair(dev1_name, dev2_name):
"""Create a pair of veth devices with the specified names,
deleting any previous devices with those names.
"""
for dev in [dev1_name, dev2_name]:
if _device_exists(dev):
try:
utils.execute('ip', 'link', 'delete', dev1_name,
run_as_root=True, check_exit_code=[0, 2, 254])
except exception.ProcessExecutionError:
LOG.exception("Error clearing stale veth %s" % dev)
utils.execute('ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
'name', dev2_name, run_as_root=True)
for dev in [dev1_name, dev2_name]:
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
utils.execute('ip', 'link', 'set', dev, 'promisc', 'on',
run_as_root=True)
# Similar to compute virt layers, the Linux network node
# code uses a flexible driver model to support different ways
# of creating ethernet interfaces and attaching them to the network.

View File

@ -56,6 +56,8 @@ LOG = logging.getLogger(__name__)
class API(base.Base):
"""API for interacting with the quantum 2.x API."""
security_group_api = None
def setup_networks_on_host(self, context, instance, host=None,
teardown=False):
"""Setup or teardown the network structures."""
@ -131,6 +133,9 @@ class API(base.Base):
" failure: %(exception)s")
LOG.debug(msg, {'portid': port_id,
'exception': ex})
self.trigger_security_group_members_refresh(context, instance)
return self.get_instance_nw_info(context, instance, networks=nets)
def deallocate_for_instance(self, context, instance, **kwargs):
@ -146,6 +151,7 @@ class API(base.Base):
except Exception as ex:
LOG.exception(_("Failed to delete quantum port %(portid)s ")
% {'portid': port['id']})
self.trigger_security_group_members_refresh(context, instance)
@refresh_cache
def get_instance_nw_info(self, context, instance, networks=None):
@ -219,6 +225,21 @@ class API(base.Base):
return [{'instance_uuid': port['device_id']} for port in ports
if port['device_id']]
def trigger_security_group_members_refresh(self, context, instance_ref):
# used to avoid circular import
if not self.security_group_api:
from nova.compute import api as compute_api
self.security_group_api = compute_api.SecurityGroupAPI()
admin_context = context.elevated()
group_ids = [group['id'] for group in instance_ref['security_groups']]
self.security_group_api.trigger_members_refresh(admin_context,
group_ids)
self.security_group_api.trigger_handler('security_group_members',
admin_context, group_ids)
@refresh_cache
def associate_floating_ip(self, context, instance,
floating_address, fixed_address,
@ -339,13 +360,24 @@ class API(base.Base):
data = quantumv2.get_client(context).list_subnets(**search_opts)
ipam_subnets = data.get('subnets', [])
subnets = []
for subnet in ipam_subnets:
subnet_dict = {'cidr': subnet['cidr'],
'gateway': network_model.IP(
address=subnet['gateway_ip'],
type='gateway'),
}
# TODO(gongysh) deal with dhcp
# attempt to populate DHCP server field
search_opts = {'network_id': subnet['network_id'],
'device_owner': 'network:dhcp'}
data = quantumv2.get_client(context).list_ports(**search_opts)
dhcp_ports = data.get('ports', [])
for p in dhcp_ports:
for ip_pair in p['fixed_ips']:
if ip_pair['subnet_id'] == subnet['id']:
subnet_dict['dhcp_server'] = ip_pair['ip_address']
break
subnet_object = network_model.Subnet(**subnet_dict)
for dns in subnet.get('dns_nameservers', []):

View File

@ -143,7 +143,8 @@ class TestQuantumv2(test.TestCase):
'bff4a5a6b9eb4ea2a6efec6eefb77936')
self.instance = {'project_id': '9d049e4b60b64716978ab415e6fbd5c0',
'uuid': str(utils.gen_uuid()),
'display_name': 'test_instance'}
'display_name': 'test_instance',
'security_groups': []}
self.nets1 = [{'id': 'my_netid1',
'name': 'my_netname1',
'tenant_id': 'my_tenantid'}]
@ -164,6 +165,8 @@ class TestQuantumv2(test.TestCase):
'fixed_ips': [{'ip_address': '10.0.1.2',
'subnet_id': 'my_subid1'}],
'mac_address': 'my_mac1', }]
self.dhcp_port_data1 = [{'fixed_ips': [{'ip_address': '10.0.1.9',
'subnet_id': 'my_subid1'}]}]
self.port_data2 = []
self.port_data2.append(self.port_data1[0])
self.port_data2.append({'network_id': 'my_netid2',
@ -173,11 +176,15 @@ class TestQuantumv2(test.TestCase):
'fixed_ips': [{'ip_address': '10.0.2.2',
'subnet_id': 'my_subid2'}],
'mac_address': 'my_mac2', })
self.subnet_data1 = [{'cidr': '10.0.1.0/24',
self.subnet_data1 = [{'id': 'my_subid1',
'cidr': '10.0.1.0/24',
'network_id': 'my_netid1',
'gateway_ip': '10.0.1.1',
'dns_nameservers': ['8.8.1.1', '8.8.1.2']}]
self.subnet_data2 = []
self.subnet_data2.append({'cidr': '10.0.2.0/24',
self.subnet_data2.append({'id': 'my_subid2',
'cidr': '10.0.2.0/24',
'network_id': 'my_netid2',
'gateway_ip': '10.0.2.1',
'dns_nameservers': ['8.8.2.1', '8.8.2.2']})
@ -221,6 +228,10 @@ class TestQuantumv2(test.TestCase):
self.moxed_client.list_subnets(
id=mox.SameElementsAs(['my_subid%s' % i])).AndReturn(
{'subnets': subnet_data})
self.moxed_client.list_ports(
network_id=subnet_data[0]['network_id'],
device_owner='network:dhcp').AndReturn(
{'ports': []})
self.mox.ReplayAll()
nw_inf = api.get_instance_nw_info(self.context, self.instance)
for i in xrange(0, number):
@ -248,6 +259,10 @@ class TestQuantumv2(test.TestCase):
self.moxed_client.list_subnets(
id=mox.SameElementsAs(['my_subid1'])).AndReturn(
{'subnets': self.subnet_data1})
self.moxed_client.list_ports(
network_id='my_netid1',
device_owner='network:dhcp').AndReturn(
{'ports': self.dhcp_port_data1})
self.mox.ReplayAll()
nw_inf = api.get_instance_nw_info(self.context,
self.instance,

View File

@ -154,3 +154,19 @@ class LibvirtVifTestCase(test.TestCase):
self.assertEquals(script, "")
d.unplug(None, (self.net, self.mapping))
def test_quantum_hybrid_driver(self):
d = vif.LibvirtHybridOVSBridgeDriver()
xml = self._get_instance_xml(d)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
self.assertEqual(len(ret), 1)
node = ret[0]
self.assertEqual(node.get("type"), "bridge")
br_name = node.find("source").get("bridge")
self.assertEqual(br_name, self.net['bridge'])
mac = node.find("mac").get("address")
self.assertEqual(mac, self.mapping['mac'])
d.unplug(None, (self.net, self.mapping))

View File

@ -45,6 +45,8 @@ FLAGS = flags.FLAGS
FLAGS.register_opts(libvirt_vif_opts)
flags.DECLARE('libvirt_type', 'nova.virt.libvirt.driver')
LINUX_DEV_LEN = 14
class LibvirtBridgeDriver(vif.VIFDriver):
"""VIF driver for Linux bridge."""
@ -114,12 +116,29 @@ class LibvirtBridgeDriver(vif.VIFDriver):
class LibvirtOpenVswitchDriver(vif.VIFDriver):
"""VIF driver for Open vSwitch that uses type='ethernet'
libvirt XML. Used for libvirt versions that do not support
OVS virtual port XML (0.9.10 or earlier)."""
"""VIF driver for Open vSwitch that uses libivrt type='ethernet'
Used for libvirt versions that do not support
OVS virtual port XML (0.9.10 or earlier).
"""
def get_dev_name(self, iface_id):
return "tap" + iface_id[0:11]
return ("tap" + iface_id)[:LINUX_DEV_LEN]
def create_ovs_vif_port(self, dev, iface_id, mac, instance_id):
utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
FLAGS.libvirt_ovs_bridge, dev,
'--', 'set', 'Interface', dev,
'external-ids:iface-id=%s' % iface_id,
'external-ids:iface-status=active',
'external-ids:attached-mac=%s' % mac,
'external-ids:vm-uuid=%s' % instance_id,
run_as_root=True)
def delete_ovs_vif_port(self, dev):
utils.execute('ovs-vsctl', 'del-port', FLAGS.libvirt_ovs_bridge,
dev, run_as_root=True)
utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
def plug(self, instance, vif):
network, mapping = vif
@ -138,17 +157,9 @@ class LibvirtOpenVswitchDriver(vif.VIFDriver):
# Second option: tunctl
utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
FLAGS.libvirt_ovs_bridge, dev,
'--', 'set', 'Interface', dev,
"external-ids:iface-id=%s" % iface_id,
'--', 'set', 'Interface', dev,
"external-ids:iface-status=active",
'--', 'set', 'Interface', dev,
"external-ids:attached-mac=%s" % mapping['mac'],
'--', 'set', 'Interface', dev,
"external-ids:vm-uuid=%s" % instance['uuid'],
run_as_root=True)
self.create_ovs_vif_port(dev, iface_id, mapping['mac'],
instance['uuid'])
conf = config.LibvirtConfigGuestInterface()
@ -162,14 +173,76 @@ class LibvirtOpenVswitchDriver(vif.VIFDriver):
return conf
def unplug(self, instance, vif):
"""Unplug the VIF from the network by deleting the port from
the bridge."""
network, mapping = vif
dev = self.get_dev_name(mapping['vif_uuid'])
"""Unplug the VIF by deleting the port from the bridge."""
try:
utils.execute('ovs-vsctl', 'del-port',
FLAGS.libvirt_ovs_bridge, dev, run_as_root=True)
utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
network, mapping = vif
self.delete_ovs_vif_port(self.get_dev_name(mapping['vif_uuid']))
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
LibvirtOpenVswitchDriver):
"""VIF driver that uses OVS + Linux Bridge for iptables compatibility.
Enables the use of OVS-based Quantum plugins while at the same
time using iptables-based filtering, which requires that vifs be
plugged into a linux bridge, not OVS. IPtables filtering is useful for
in particular for Nova security groups.
"""
def get_br_name(self, iface_id):
return ("qbr" + iface_id)[:LINUX_DEV_LEN]
def get_veth_pair_names(self, iface_id):
return (("qvb%s" % iface_id)[:LINUX_DEV_LEN],
("qvo%s" % iface_id)[:LINUX_DEV_LEN])
def plug(self, instance, vif):
"""Plug using hybrid strategy
Create a per-VIF linux bridge, then link that bridge to the OVS
integration bridge via a veth device, setting up the other end
of the veth device just like a normal OVS port. Then boot the
VIF on the linux bridge using standard libvirt mechanisms
"""
network, mapping = vif
iface_id = mapping['vif_uuid']
br_name = self.get_br_name(iface_id)
v1_name, v2_name = self.get_veth_pair_names(iface_id)
linux_net._create_veth_pair(v1_name, v2_name)
if not linux_net._device_exists(br_name):
utils.execute('brctl', 'addbr', br_name, run_as_root=True)
utils.execute('ip', 'link', 'set', br_name, 'up', run_as_root=True)
utils.execute('brctl', 'addif', br_name, v1_name, run_as_root=True)
self.create_ovs_vif_port(v2_name, iface_id, mapping['mac'],
instance['uuid'])
network['bridge'] = br_name
return self._get_configurations(instance, network, mapping)
def unplug(self, instance, vif):
"""UnPlug using hybrid strategy
Unhook port from OVS, unhook port from bridge, delete
bridge, and delete both veth devices.
"""
try:
network, mapping = vif
iface_id = mapping['vif_uuid']
br_name = self.get_br_name(iface_id)
v1_name, v2_name = self.get_veth_pair_names(iface_id)
utils.execute('brctl', 'delif', br_name, v1_name, run_as_root=True)
utils.execute('ip', 'link', 'set', br_name, 'down',
run_as_root=True)
utils.execute('brctl', 'delbr', br_name, run_as_root=True)
self.delete_ovs_vif_port(v2_name)
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
@ -203,7 +276,7 @@ class QuantumLinuxBridgeVIFDriver(vif.VIFDriver):
"""VIF driver for Linux Bridge when running Quantum."""
def get_dev_name(self, iface_id):
return "tap" + iface_id[0:11]
return ("tap" + iface_id)[:LINUX_DEV_LEN]
def plug(self, instance, vif):
network, mapping = vif
@ -225,8 +298,7 @@ class QuantumLinuxBridgeVIFDriver(vif.VIFDriver):
return conf
def unplug(self, instance, vif):
"""Unplug the VIF from the network by deleting the port from
the bridge."""
"""Unplug the VIF by deleting the port from the bridge."""
network, mapping = vif
dev = self.get_dev_name(mapping['vif_uuid'])
try: