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:
parent
0749ffc3be
commit
5a470f89b6
|
@ -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.
|
||||
|
|
|
@ -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', []):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue