summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-09-05 18:13:34 +0000
committerGerrit Code Review <review@openstack.org>2012-09-05 18:13:34 +0000
commitfb84ed7b32520b11d4a6273eb002b1f1abd2fbe8 (patch)
tree20f4006c2471a2119ec1dcdf589ac5c3247f31bd
parent4ccc770541ab7263c9181490ee5d3d0602b706ce (diff)
parent5a470f89b6a508d578b89a1687d327efbc834346 (diff)
Merge "fix issues with Nova security groups and Quantum"
-rw-r--r--nova/network/linux_net.py20
-rw-r--r--nova/network/quantumv2/api.py34
-rw-r--r--nova/tests/network/test_quantumv2.py21
-rw-r--r--nova/tests/test_libvirt_vif.py16
-rw-r--r--nova/virt/libvirt/vif.py120
5 files changed, 183 insertions, 28 deletions
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 40f75f2ff..b9988588c 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -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.
diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py
index 50c517f7d..b76e5f703 100644
--- a/nova/network/quantumv2/api.py
+++ b/nova/network/quantumv2/api.py
@@ -57,6 +57,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."""
@@ -147,6 +149,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):
@@ -162,6 +167,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):
@@ -233,6 +239,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,
@@ -350,13 +371,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', []):
diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py
index 4c41cfd39..19ba84edf 100644
--- a/nova/tests/network/test_quantumv2.py
+++ b/nova/tests/network/test_quantumv2.py
@@ -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'}]
@@ -167,6 +168,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',
@@ -176,11 +179,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']})
@@ -226,6 +233,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):
@@ -253,6 +264,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,
diff --git a/nova/tests/test_libvirt_vif.py b/nova/tests/test_libvirt_vif.py
index 9e5ce24bd..ca52f14ed 100644
--- a/nova/tests/test_libvirt_vif.py
+++ b/nova/tests/test_libvirt_vif.py
@@ -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))
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
index 0337ddc89..1a64765b1 100644
--- a/nova/virt/libvirt/vif.py
+++ b/nova/virt/libvirt/vif.py
@@ -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."""
+ """Unplug the VIF by deleting the port from the bridge."""
+ try:
+ 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
- dev = self.get_dev_name(mapping['vif_uuid'])
+ 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:
- 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
+ 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: