Merge "Fuel plugins: Apply patchset to support neutron security group"

This commit is contained in:
Jenkins 2016-03-24 14:38:15 +00:00 committed by Gerrit Code Review
commit d91e346930
6 changed files with 1921 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import re
from socket import inet_ntoa
from struct import pack
import subprocess
import sys
import yaml
@ -373,6 +374,24 @@ def enable_linux_bridge(himn, username, password):
ssh(himn, username, password, 'rm -f /etc/modprobe.d/blacklist-bridge')
def patch_compute_xenapi():
"""replace folder xenapi to add patches which are not merged to upstream"""
# TODO(huanxie): need to confirm the overall patchset list
patchset_dir = sys.path[0]
patchfile_list = ['%s/patchset/vif-plug.patch' % patchset_dir,
'%s/patchset/nova-neutron-race-condition.patch' % patchset_dir,
'%s/patchset/ovs-interim-bridge.patch' % patchset_dir,
'%s/patchset/neutron-security-group.patch' % patchset_dir]
for patch_file in patchfile_list:
execute('patch', '-d', DIST_PACKAGES_DIR, '-p1', '-i', patch_file)
def patch_neutron_ovs_agent():
patchset_dir = sys.path[0]
patch_file = '%s/patchset/neutron-rootwrap-xen-dom0.patch' % patchset_dir
execute('patch', '-d', '/usr/', '-p1', '-i', patch_file)
if __name__ == '__main__':
install_xenapi_sdk()
astute = get_astute(ASTUTE_PATH)
@ -397,6 +416,7 @@ if __name__ == '__main__':
forward_port('br-mgmt', himn_eth, HIMN_IP, '80')
create_novacompute_conf(HIMN_IP, username, password, public_ip)
patch_compute_xenapi()
restart_services('nova-compute')
install_logrotate_script(HIMN_IP, username, password)
@ -406,4 +426,5 @@ if __name__ == '__main__':
br_mappings = find_bridge_mappings(astute, HIMN_IP,
username, password)
modify_neutron_ovs_agent_conf(INT_BRIDGE, br_mappings)
patch_neutron_ovs_agent()
restart_services('neutron-plugin-openvswitch-agent')

View File

@ -0,0 +1,24 @@
diff --git a/bin/neutron-rootwrap-xen-dom0 b/bin/neutron-rootwrap-xen-dom0
index b4e2e31..1d73883 100755
--- a/bin/neutron-rootwrap-xen-dom0
+++ b/bin/neutron-rootwrap-xen-dom0
@@ -113,11 +113,14 @@ def run_command(url, username, password, user_args, cmd_input):
try:
session = XenAPI.Session(url)
session.login_with_password(username, password)
- host = session.xenapi.session.get_this_host(session.handle)
- result = session.xenapi.host.call_plugin(
- host, 'netwrap', 'run_command',
- {'cmd': json.dumps(user_args), 'cmd_input': json.dumps(cmd_input)})
- return json.loads(result)
+ try:
+ host = session.xenapi.session.get_this_host(session.handle)
+ result = session.xenapi.host.call_plugin(
+ host, 'netwrap', 'run_command',
+ {'cmd': json.dumps(user_args), 'cmd_input': json.dumps(cmd_input)})
+ return json.loads(result)
+ finally:
+ session.xenapi.session.logout()
except Exception as e:
traceback.print_exc()
sys.exit(RC_XENAPI_ERROR)

View File

@ -0,0 +1,417 @@
diff --git a/nova/tests/unit/virt/xenapi/test_vif.py b/nova/tests/unit/virt/xenapi/test_vif.py
index 65e3070..a314746 100644
--- a/nova/tests/unit/virt/xenapi/test_vif.py
+++ b/nova/tests/unit/virt/xenapi/test_vif.py
@@ -22,6 +22,8 @@ from nova.network import model
from nova.tests.unit.virt.xenapi import stubs
from nova.virt.xenapi import network_utils
from nova.virt.xenapi import vif
+from nova.virt.xenapi import vm_utils
+from oslo_serialization import jsonutils
fake_vif = {
'created_at': None,
@@ -194,69 +196,144 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
self.assertEqual('fake_vif_ref', ret_vif_ref)
self.assertTrue(mock_create_vif_interim_network.called)
- @patch.object(vif.XenAPIOpenVswitchDriver, 'get_vif_interim_net_name',
- return_value="fake_net_name")
- @patch.object(vif.XenAPIOpenVswitchDriver, '_get_patch_port_pair_names',
- return_value=("fake_port_name1", "fake_port_name2"))
- @patch.object(network_utils, 'find_network_with_name_label',
- return_value='fake_network')
+ @patch.object(vm_utils, 'lookup', return_value='fake_vm_ref')
@patch.object(vif.XenVIFDriver, '_get_vif_ref',
return_value='fake_vif_ref')
- def test_unplug(self, mock_get_vif_ref,
+ @patch.object(network_utils, 'find_network_with_name_label',
+ return_value='fake_network')
+ @patch.object(vif.XenAPIOpenVswitchDriver, '_device_exists',
+ return_value=True)
+ def test_unplug(self, mock_lookup, mock_get_vif_ref,
mock_find_network_with_name_label,
- mock_get_patch_port_pair_names,
- mock_get_vif_interim_net_name):
+ mock_device_exists):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
self.ovs_driver.unplug(instance, fake_vif, vm_ref)
- del_p1 = '["ovs-vsctl", "--", "--if-exists", ' \
- '"del-port", "fake_bridge", "fake_port_name1"]'
- del_p2 = '["ovs-vsctl", "--", "--if-exists", ' \
- '"del-port", "xapi1", "fake_port_name2"]'
- del_br = '["ovs-vsctl", "--", "--if-exists", ' \
- '"del-br", "fake_bridge"]'
+ # interim bridge
+ patch_port1 = ("vif%s" % fake_vif['id'])[:14]
+ del_p1 = {'cmd': jsonutils.dumps(
+ ['ovs-vsctl', '--', '--if-exists', 'del-port',
+ 'fake_bridge', patch_port1])}
+ del_br = {'cmd': jsonutils.dumps(
+ ['ovs-vsctl', '--', '--if-exists', 'del-br',
+ 'fake_bridge'])}
+
+ # linux bridge
+ qbr_name = ("qbr%s" % fake_vif['id'])[:14]
+ tap_name = ("tap%s" % fake_vif['id'])[:14]
+ qvb_name = ("qvb%s" % fake_vif['id'])[:14]
+ del_tap = {'cmd': jsonutils.dumps(
+ ['brctl', 'delif', qbr_name, tap_name])}
+ del_link_tap = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'delete', tap_name])}
+ del_qvb = {'cmd': jsonutils.dumps(
+ ['brctl', 'delif', qbr_name, qvb_name])}
+ del_link_qvb = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'delete', qvb_name])}
+ down_link_qbr = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', qbr_name, 'down'])}
+ del_qbr = {'cmd': jsonutils.dumps(['brctl', 'delbr', qbr_name])}
expected_calls = [call.call_xenapi('VIF.destroy', 'fake_vif_ref'),
call.call_xenapi('network.get_VIFs', 'fake_network'),
call.call_xenapi('network.get_bridge', 'fake_network'),
- call.call_plugin('xenhost', 'network_config', {'cmd': del_p1}),
- call.call_plugin('xenhost', 'network_config', {'cmd': del_p2}),
+ call.call_plugin('xenhost', 'network_config', del_p1),
call.call_xenapi('network.destroy', 'fake_network'),
- call.call_plugin('xenhost', 'network_config', {'cmd': del_br})]
+ call.call_plugin('xenhost', 'network_config', del_br),
+ call.call_plugin('xenhost', 'network_config', del_tap),
+ call.call_plugin('xenhost', 'network_config', del_link_tap),
+ call.call_plugin('xenhost', 'network_config', del_qvb),
+ call.call_plugin('xenhost', 'network_config', del_link_qvb),
+ call.call_plugin('xenhost', 'network_config', down_link_qbr),
+ call.call_plugin('xenhost', 'network_config', del_qbr)]
self._session.assert_has_calls(expected_calls)
- @patch.object(vif.XenAPIOpenVswitchDriver, 'get_vif_interim_net_name',
- return_value="fake_net_name")
- @patch.object(vif.XenAPIOpenVswitchDriver, '_get_patch_port_pair_names',
- return_value=("fake_port_name1", "fake_port_name2"))
- def test_post_start_actions(self, mock_get_patch_port_pair_names,
- mock_get_vif_interim_net_name):
+ @patch.object(vif.XenAPIOpenVswitchDriver, '_device_exists',
+ return_value=False)
+ def test_post_start_actions(self, mock_device_exists):
vif_ref = "fake_vif_ref"
instance = {'name': 'fake_instance_name'}
self.ovs_driver.post_start_actions(instance, vif_ref)
- add_p1 = '["ovs-vsctl", "--", ' \
- '"--if-exists", "del-port", "fake_port_name1", "--", ' \
- '"add-port", "fake_bridge", "fake_port_name1", "--", ' \
- '"set", "interface", "fake_port_name1", ' \
- '"type=patch", "options:peer=fake_port_name2"]'
- add_p2 = '["ovs-vsctl", "--", ' \
- '"--if-exists", "del-port", "fake_port_name2", "--", ' \
- '"add-port", "xapi1", "fake_port_name2", "--", ' \
- '"set", "interface", "fake_port_name2", ' \
- '"type=patch", "options:peer=fake_port_name1"]'
- ext_map = '["ovs-vsctl", "set", "Interface", "fake_port_name2", ' \
- '"external-ids:attached-mac=00:00:00:00:00:00", ' \
- '"external-ids:iface-id=fake-nicira-iface-id", ' \
- '"external-ids:iface-status=active", ' \
- '"external-ids:xs-vif-uuid=fake_uuid"]'
+ # create linux bridge
+ qbr_name = ("qbr%s" % fake_vif['id'])[:14]
+ add_qbr = {'cmd': jsonutils.dumps(['brctl', 'addbr', qbr_name])}
+ set_fd = {'cmd': jsonutils.dumps(['brctl', 'setfd', qbr_name, '0'])}
+ off_stp = {'cmd': jsonutils.dumps(['brctl', 'stp', qbr_name, 'off'])}
+ up_qbr_link = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', qbr_name, 'up'])}
+
+ # create veth pair, qvb/qvo
+ qvb_name = ("qvb%s" % fake_vif['id'])[:14]
+ qvo_name = ("qvo%s" % fake_vif['id'])[:14]
+ qvb_qvo_veth_pair = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'add', qvb_name, 'type', 'veth',
+ 'peer', 'name', qvo_name])}
+ qvb_link_up = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', qvb_name, 'up'])}
+ qvb_promisc_on = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', qvb_name, 'promisc', 'on'])}
+ qvo_link_up = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', qvo_name, 'up'])}
+ qvo_promisc_on = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', qvo_name, 'promisc', 'on'])}
+
+ # add qvb/qvo port
+ add_qvb = {'cmd': jsonutils.dumps(
+ ['brctl', 'addif', qbr_name, qvb_name])}
+ del_add_qvo = {'cmd': jsonutils.dumps(
+ ['ovs-vsctl', '--', '--if-exists', 'del-port',
+ qvo_name, '--', 'add-port', 'xapi1', qvo_name])}
+ ext_map = {'cmd': jsonutils.dumps(
+ ["ovs-vsctl", "set", "Interface", qvo_name,
+ "external-ids:attached-mac=%s" % fake_vif['address'],
+ "external-ids:iface-id=%s" % fake_vif['id'],
+ "external-ids:iface-status=active",
+ "external-ids:xs-vif-uuid=%s" % fake_vif['uuid']])}
+
+ # create veth tap/patch port
+ patch_port1 = ("vif%s" % fake_vif['id'])[:14]
+ tap_port = ("tap%s" % fake_vif['id'])[:14]
+ tap_patch_veth_pair = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'add', tap_port, 'type',
+ 'veth', 'peer', 'name', patch_port1])}
+ up_tap_link = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', tap_port, 'up'])}
+ tap_promisc_on = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', tap_port, 'promisc', 'on'])}
+ up_patch_link = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', patch_port1, 'up'])}
+ patch_promisc_on = {'cmd': jsonutils.dumps(
+ ['ip', 'link', 'set', patch_port1, 'promisc', 'on'])}
+ add_tap = {'cmd': jsonutils.dumps(
+ ['brctl', 'addif', qbr_name, tap_port])}
+ add_patch = {'cmd': jsonutils.dumps(
+ ['ovs-vsctl', '--', '--if-exists', 'del-port',
+ patch_port1, '--', 'add-port', 'fake_bridge',
+ patch_port1])}
expected_calls = [call.call_xenapi('VIF.get_record', vif_ref),
call.call_xenapi('network.get_bridge', 'fake_network'),
- call.call_plugin('xenhost', 'network_config', {'cmd': add_p1}),
- call.call_plugin('xenhost', 'network_config', {'cmd': add_p2}),
- call.call_plugin('xenhost', 'network_config', {'cmd': ext_map})]
+ call.call_plugin('xenhost', 'network_config', add_qbr),
+ call.call_plugin('xenhost', 'network_config', set_fd),
+ call.call_plugin('xenhost', 'network_config', off_stp),
+ call.call_plugin('xenhost', 'network_config', up_qbr_link),
+ call.call_plugin('xenhost', 'network_config', qvb_qvo_veth_pair),
+ call.call_plugin('xenhost', 'network_config', qvb_link_up),
+ call.call_plugin('xenhost', 'network_config', qvb_promisc_on),
+ call.call_plugin('xenhost', 'network_config', qvo_link_up),
+ call.call_plugin('xenhost', 'network_config', qvo_promisc_on),
+ call.call_plugin('xenhost', 'network_config', add_qvb),
+ call.call_plugin('xenhost', 'network_config', del_add_qvo),
+ call.call_plugin('xenhost', 'network_config', ext_map),
+ call.call_plugin('xenhost', 'network_config', tap_patch_veth_pair),
+ call.call_plugin('xenhost', 'network_config', up_tap_link),
+ call.call_plugin('xenhost', 'network_config', tap_promisc_on),
+ call.call_plugin('xenhost', 'network_config', up_patch_link),
+ call.call_plugin('xenhost', 'network_config', patch_promisc_on),
+ call.call_plugin('xenhost', 'network_config', add_tap),
+ call.call_plugin('xenhost', 'network_config', add_patch)]
self._session.assert_has_calls(expected_calls)
@patch.object(network_utils, 'find_network_with_name_label',
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py
index 5c1ac29..bae0a84 100644
--- a/nova/virt/xenapi/vif.py
+++ b/nova/virt/xenapi/vif.py
@@ -230,11 +230,11 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
def unplug(self, instance, vif, vm_ref):
"""unplug vif:
- 1. unplug and destroy vif.
- 2. delete the patch port pair between the integration bridge and
- the interim network.
- 3. destroy the interim network
- 4. delete the OVS bridge service for the interim network
+ 1. delete the patch port pair between the integration bridge and
+ the qbr linux bridge(if exist) and the interim network.
+ 2. destroy the interim network
+ 3. delete the OVS bridge service for the interim network
+ 4. delete linux bridge qbr and related ports if exist
"""
try:
super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref)
@@ -255,27 +255,124 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
{'vif_id': vif['id']})
bridge_name = self._session.call_xenapi('network.get_bridge',
network)
- patch_port1, patch_port2 = self._get_patch_port_pair_names(
- vif['id'])
- # delete the patch port pair
+ patch_port1, tap_name = self._get_patch_port_pair_names(vif['id'])
+
self._del_ovs_port(bridge_name, patch_port1)
- self._del_ovs_port(CONF.xenserver.ovs_integration_bridge,
- patch_port2)
- LOG.debug('destroying network: network=%(network)s,'
- 'bridge=%(br)s',
+ LOG.debug('destroying network: network=%(network)s, bridge=%(br)s',
{'network': network, 'br': bridge_name})
self._session.call_xenapi('network.destroy', network)
# delete bridge if it still exists.
# As there is patch port existing on this bridge when destroying
- # the VM vif (which happens when shutdown the VM), the bridge
- # won't be destroyed automatically by XAPI. So let's destroy it
- # at here.
+ # VM vif (which happens when shutdown the VM), the bridge won't be
+ # destroyed automatically by XAPI. So let's destroy it at here.
self._del_ovs_br(bridge_name)
+
+ qbr_name = self._get_qbr_name(vif['id'])
+ qvb_name, qvo_name = self._get_veth_pair_names(vif['id'])
+ if self._device_exists(qbr_name):
+ # delete tap port, qvb port and qbr
+ LOG.debug(
+ "destroy linux bridge %(qbr)s when unplug vif %(vif)s",
+ {'qbr': qbr_name, 'vif': vif['id']})
+ self._delete_linux_port(qbr_name, tap_name)
+ self._delete_linux_port(qbr_name, qvb_name)
+ self._delete_linux_bridge(qbr_name)
+ self._del_ovs_port(CONF.xenserver.ovs_integration_bridge, qvo_name)
except Exception as e:
LOG.debug("Fail to unplug vif %(vif)s, exception:%(exception)s",
{'vif': vif, 'exception': e}, instance=instance)
+ def _get_qbr_name(self, iface_id):
+ return ("qbr" + iface_id)[:network_model.NIC_NAME_LEN]
+
+ def _get_veth_pair_names(self, iface_id):
+ return (("qvb%s" % iface_id)[:network_model.NIC_NAME_LEN],
+ ("qvo%s" % iface_id)[:network_model.NIC_NAME_LEN])
+
+ def _device_exists(self, device):
+ """Check if ethernet device exists."""
+ try:
+ self._exec_dom0_cmd(['ip', 'link', 'show', device])
+ return True
+ except Exception:
+ # Swallow exception from plugin, since this indicates the device
+ # doesn't exist
+ return False
+
+ def _delete_net_dev(self, dev):
+ """Delete a network device only if it exists."""
+ if self._device_exists(dev):
+ LOG.debug("delete network device '%s'", dev)
+ cmd_args = ['ip', 'link', 'delete', dev]
+ self._exec_dom0_cmd(cmd_args)
+
+ def _create_veth_pair(self, 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]:
+ self._delete_net_dev(dev)
+ LOG.debug("Create veth pair, port1:%(qvb)s, port2:%(qvo)s",
+ {'qvb': dev1_name, 'qvo': dev2_name})
+ cmd_args = ['ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
+ 'name', dev2_name]
+ self._exec_dom0_cmd(cmd_args)
+ for dev in [dev1_name, dev2_name]:
+ cmd_args = ['ip', 'link', 'set', dev, 'up']
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['ip', 'link', 'set', dev, 'promisc', 'on']
+ self._exec_dom0_cmd(cmd_args)
+
+ def _create_linux_bridge(self, vif_rec):
+ """create a qbr linux bridge for neutron security group
+ """
+ iface_id = vif_rec['other_config']['nicira-iface-id']
+ linux_br_name = self._get_qbr_name(iface_id)
+ if not self._device_exists(linux_br_name):
+ LOG.debug("Create linux bridge %s", linux_br_name)
+ cmd_args = ['brctl', 'addbr', linux_br_name]
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['brctl', 'setfd', linux_br_name, '0']
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['brctl', 'stp', linux_br_name, 'off']
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['ip', 'link', 'set', linux_br_name, 'up']
+ self._exec_dom0_cmd(cmd_args)
+
+ qvb_name, qvo_name = self._get_veth_pair_names(iface_id)
+ if not self._device_exists(qvo_name):
+ self._create_veth_pair(qvb_name, qvo_name)
+ cmd_args = ['brctl', 'addif', linux_br_name, qvb_name]
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', qvo_name,
+ '--', 'add-port',
+ CONF.xenserver.ovs_integration_bridge, qvo_name]
+ self._exec_dom0_cmd(cmd_args)
+ self._map_external_ids_with_vif(qvo_name, vif_rec)
+ return linux_br_name
+
+ def _delete_linux_port(self, qbr_name, port_name):
+ try:
+ # delete port in linux bridge
+ cmd_args = ['brctl', 'delif', qbr_name, port_name]
+ self._exec_dom0_cmd(cmd_args)
+ self._delete_net_dev(port_name)
+ except Exception:
+ LOG.debug("Fail to delete linux port %(port_name)s on bridge"
+ "%(qbr_name)s",
+ {'port_name': port_name, 'qbr_name': qbr_name})
+
+ def _delete_linux_bridge(self, qbr_name):
+ try:
+ # delete linux bridge qbrxxx
+ cmd_args = ['ip', 'link', 'set', qbr_name, 'down']
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['brctl', 'delbr', qbr_name]
+ self._exec_dom0_cmd(cmd_args)
+ except Exception:
+ LOG.debug("Fail to delete linux bridge %s", qbr_name)
+
def post_start_actions(self, instance, vif_ref):
"""Do needed actions post vif start:
plug the interim ovs bridge to the integration bridge;
@@ -287,10 +384,10 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
bridge_name = self._session.call_xenapi('network.get_bridge',
network_ref)
iface_id = vif_rec['other_config']['nicira-iface-id']
- patch_port1, patch_port2 = self._get_patch_port_pair_names(iface_id)
+ patch_port1, tap_name = self._get_patch_port_pair_names(iface_id)
LOG.debug('plug_ovs_bridge: port1=%(port1)s, port2=%(port2)s,'
'network_ref=%(network_ref)s, bridge_name=%(bridge_name)s',
- {'port1': patch_port1, 'port2': patch_port2,
+ {'port1': patch_port1, 'port2': tap_name,
'network_ref': network_ref,
'bridge_name': bridge_name})
if bridge_name is None:
@@ -298,10 +395,15 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
"VM:%(vm_name)s"),
{'vif_ref': vif_ref, 'vm_name': instance['name']})
- self._add_patch_port(bridge_name, patch_port1, patch_port2)
- self._add_patch_port(CONF.xenserver.ovs_integration_bridge,
- patch_port2, patch_port1)
- self._map_external_ids_with_vif(patch_port2, vif_rec)
+ # Create Linux bridge qbrXXX
+ linux_br_name = self._create_linux_bridge(vif_rec)
+ LOG.debug("create veth pair for interim bridge and linux bridge")
+ self._create_veth_pair(tap_name, patch_port1)
+ cmd_args = ['brctl', 'addif', linux_br_name, tap_name]
+ self._exec_dom0_cmd(cmd_args)
+ cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', patch_port1,
+ '--', 'add-port', bridge_name, patch_port1]
+ self._exec_dom0_cmd(cmd_args)
def get_vif_interim_net_name(self, vif):
return ("net-" + vif['id'])[:network_model.NIC_NAME_LEN]
@@ -326,8 +428,8 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
return network_ref
def _get_patch_port_pair_names(self, iface_id):
- return (("pp1-%s" % iface_id)[:network_model.NIC_NAME_LEN],
- ("pp2-%s" % iface_id)[:network_model.NIC_NAME_LEN])
+ return (("vif%s" % iface_id)[:network_model.NIC_NAME_LEN],
+ ("tap%s" % iface_id)[:network_model.NIC_NAME_LEN])
def _add_patch_port(self, bridge_name, port_name, peer_port_name):
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port_name,
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
index 4bf85ac..80f263f 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
@@ -214,9 +214,11 @@ def iptables_config(session, args):
def network_config(session, args):
- # function to config OVS bridge
+ # function to config OVS bridge and Linux bridge
ALLOWED_CMDS = [
'ovs-vsctl',
+ 'brctl',
+ 'ip'
]
cmd = json.loads(args.get('cmd'))
if cmd is None or cmd == []:

View File

@ -0,0 +1,368 @@
diff --git a/nova/tests/unit/virt/xenapi/test_vmops.py b/nova/tests/unit/virt/xenapi/test_vmops.py
index 58c64c0..33bec73 100644
--- a/nova/tests/unit/virt/xenapi/test_vmops.py
+++ b/nova/tests/unit/virt/xenapi/test_vmops.py
@@ -28,6 +28,7 @@ from nova import test
from nova.tests.unit import fake_flavor
from nova.tests.unit import fake_instance
from nova.tests.unit.virt.xenapi import stubs
+from nova import utils
from nova.virt import fake
from nova.virt.xenapi import agent as xenapi_agent
from nova.virt.xenapi.client import session as xenapi_session
@@ -315,10 +316,11 @@ class SpawnTestCase(VMOpsTestBase):
self.mox.StubOutWithMock(self.vmops.firewall_driver,
'apply_instance_filter')
self.mox.StubOutWithMock(self.vmops, '_update_last_dom_id')
+ self.mox.StubOutWithMock(self.vmops._session, 'call_xenapi')
def _test_spawn(self, name_label_param=None, block_device_info_param=None,
rescue=False, include_root_vdi=True, throw_exception=None,
- attach_pci_dev=False):
+ attach_pci_dev=False, neutron_exception=False):
self._stub_out_common()
instance = {"name": "dummy", "uuid": "fake_uuid"}
@@ -411,38 +413,55 @@ class SpawnTestCase(VMOpsTestBase):
step += 1
self.vmops._update_instance_progress(context, instance, step, steps)
- self.vmops._create_vifs(instance, vm_ref, network_info)
- self.vmops.firewall_driver.setup_basic_filtering(instance,
- network_info).AndRaise(NotImplementedError)
- self.vmops.firewall_driver.prepare_instance_filter(instance,
- network_info)
- step += 1
- self.vmops._update_instance_progress(context, instance, step, steps)
-
- if rescue:
- self.vmops._attach_orig_disks(instance, vm_ref)
+ if neutron_exception:
+ events = [('network-vif-plugged', 1)]
+ self.vmops._get_neutron_events(network_info,
+ True, True).AndReturn(events)
+ self.mox.StubOutWithMock(self.vmops, '_neutron_failed_callback')
+ self.mox.StubOutWithMock(self.vmops._virtapi,
+ 'wait_for_instance_event')
+ self.vmops._virtapi.wait_for_instance_event(instance, events,
+ deadline=300,
+ error_callback=self.vmops._neutron_failed_callback).\
+ AndRaise(exception.VirtualInterfaceCreateException)
+ else:
+ self.vmops._create_vifs(instance, vm_ref, network_info)
+ self.vmops.firewall_driver.setup_basic_filtering(instance,
+ network_info).AndRaise(NotImplementedError)
+ self.vmops.firewall_driver.prepare_instance_filter(instance,
+ network_info)
step += 1
- self.vmops._update_instance_progress(context, instance, step,
- steps)
- self.vmops._start(instance, vm_ref)
- self.vmops._wait_for_instance_to_start(instance, vm_ref)
- self.vmops._update_last_dom_id(vm_ref)
- step += 1
- self.vmops._update_instance_progress(context, instance, step, steps)
-
- self.vmops._configure_new_instance_with_agent(instance, vm_ref,
- injected_files, admin_password)
- self.vmops._remove_hostname(instance, vm_ref)
- step += 1
- self.vmops._update_instance_progress(context, instance, step, steps)
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+
+ if rescue:
+ self.vmops._attach_orig_disks(instance, vm_ref)
+ step += 1
+ self.vmops._update_instance_progress(context, instance, step,
+ steps)
+ start_pause = True
+ self.vmops._start(instance, vm_ref, start_pause=start_pause)
+ step += 1
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+ self.vmops.firewall_driver.apply_instance_filter(instance,
+ network_info)
+ step += 1
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+ self.vmops._session.call_xenapi('VM.unpause', vm_ref)
+ self.vmops._wait_for_instance_to_start(instance, vm_ref)
+ self.vmops._update_last_dom_id(vm_ref)
+ self.vmops._configure_new_instance_with_agent(instance, vm_ref,
+ injected_files, admin_password)
+ self.vmops._remove_hostname(instance, vm_ref)
+ step += 1
+ last_call = self.vmops._update_instance_progress(context, instance,
+ step, steps)
- self.vmops.firewall_driver.apply_instance_filter(instance,
- network_info)
- step += 1
- last_call = self.vmops._update_instance_progress(context, instance,
- step, steps)
if throw_exception:
last_call.AndRaise(throw_exception)
+ if throw_exception or neutron_exception:
self.vmops._destroy(instance, vm_ref, network_info=network_info)
vm_utils.destroy_kernel_ramdisk(self.vmops._session, instance,
kernel_file, ramdisk_file)
@@ -469,11 +488,25 @@ class SpawnTestCase(VMOpsTestBase):
self.assertRaises(test.TestingException, self._test_spawn,
throw_exception=test.TestingException())
+ def test_spawn_with_neutron(self):
+ self.mox.StubOutWithMock(self.vmops, '_get_neutron_events')
+ events = [('network-vif-plugged', 1)]
+ network_info = "net_info"
+ self.vmops._get_neutron_events(network_info,
+ True, True).AndReturn(events)
+ self.mox.StubOutWithMock(self.vmops,
+ '_neutron_failed_callback')
+ self._test_spawn()
+
+ def test_spawn_with_neutron_exception(self):
+ self.mox.StubOutWithMock(self.vmops, '_get_neutron_events')
+ self.assertRaises(exception.VirtualInterfaceCreateException,
+ self._test_spawn, neutron_exception=True)
+
def _test_finish_migration(self, power_on=True, resize_instance=True,
throw_exception=None, booted_from_volume=False):
self._stub_out_common()
self.mox.StubOutWithMock(volumeops.VolumeOps, "connect_volume")
- self.mox.StubOutWithMock(self.vmops._session, 'call_xenapi')
self.mox.StubOutWithMock(vm_utils, "import_all_migrated_disks")
self.mox.StubOutWithMock(self.vmops, "_attach_mapped_block_devices")
@@ -541,12 +574,14 @@ class SpawnTestCase(VMOpsTestBase):
network_info)
if power_on:
- self.vmops._start(instance, vm_ref)
- self.vmops._wait_for_instance_to_start(instance, vm_ref)
- self.vmops._update_last_dom_id(vm_ref)
+ self.vmops._start(instance, vm_ref, start_pause=True)
self.vmops.firewall_driver.apply_instance_filter(instance,
network_info)
+ if power_on:
+ self.vmops._session.call_xenapi('VM.unpause', vm_ref)
+ self.vmops._wait_for_instance_to_start(instance, vm_ref)
+ self.vmops._update_last_dom_id(vm_ref)
last_call = self.vmops._update_instance_progress(context, instance,
step=5, total_steps=5)
@@ -704,6 +739,57 @@ class SpawnTestCase(VMOpsTestBase):
self.vmops._configure_new_instance_with_agent(instance, vm_ref,
None, None)
+ @mock.patch.object(utils, 'is_neutron', return_value=True)
+ def test_get_neutron_event(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = True
+ first_boot = True
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual("network-vif-plugged", events[0][0])
+ self.assertEqual(1, events[0][1])
+ self.assertEqual("network-vif-plugged", events[1][0])
+ self.assertEqual(3, events[1][1])
+
+ @mock.patch.object(utils, 'is_neutron', return_value=False)
+ def test_get_neutron_event_not_neutron_network(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = True
+ first_boot = True
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual([], events)
+
+ @mock.patch.object(utils, 'is_neutron', return_value=True)
+ def test_get_neutron_event_power_off(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = False
+ first_boot = True
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual([], events)
+
+ @mock.patch.object(utils, 'is_neutron', return_value=True)
+ def test_get_neutron_event_not_first_boot(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = True
+ first_boot = False
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual([], events)
+
class DestroyTestCase(VMOpsTestBase):
def setUp(self):
diff --git a/nova/tests/unit/virt/xenapi/test_xenapi.py b/nova/tests/unit/virt/xenapi/test_xenapi.py
index 28b50ac..4847cfc 100644
--- a/nova/tests/unit/virt/xenapi/test_xenapi.py
+++ b/nova/tests/unit/virt/xenapi/test_xenapi.py
@@ -329,6 +329,12 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
virtual_size)
self.stubs.Set(vm_utils, '_safe_copy_vdi', fake_safe_copy_vdi)
+ def fake_update_instance_with_power_on(self,
+ vm_ref, instance, power_on):
+ self._update_last_dom_id(vm_ref)
+ self.stubs.Set(vmops.VMOps, '_update_instance_with_power_on',
+ fake_update_instance_with_power_on)
+
def tearDown(self):
fake_image.FakeImageService_reset()
super(XenAPIVMTestCase, self).tearDown()
@@ -1675,6 +1681,19 @@ class XenAPIMigrateInstance(stubs.XenAPITestBase):
self.stubs.Set(vmops.VMOps, '_inject_instance_metadata',
fake_inject_instance_metadata)
+ def fake_update_instance_with_power_on(self,
+ vm_ref, instance, power_on):
+ pass
+ self.stubs.Set(vmops.VMOps, '_update_instance_with_power_on',
+ fake_update_instance_with_power_on)
+
+ def _create_instance(self, **kw):
+ values = self.instance_values.copy()
+ values.update(kw)
+ instance = objects.Instance(context=self.context, **values)
+ instance.create()
+ return instance
+
def test_migrate_disk_and_power_off(self):
instance = db.instance_create(self.context, self.instance_values)
xenapi_fake.create_vm(instance['name'], 'Running')
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 65a41a3..1fbc15a 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -22,6 +22,7 @@ import functools
import time
import zlib
+import eventlet
from eventlet import greenthread
import netaddr
from oslo_config import cfg
@@ -321,7 +322,8 @@ class VMOps(object):
rescue=False, power_on=power_on, resize=resize_instance,
completed_callback=completed_callback)
- def _start(self, instance, vm_ref=None, bad_volumes_callback=None):
+ def _start(self, instance, vm_ref=None, bad_volumes_callback=None,
+ start_pause=False):
"""Power on a VM instance."""
vm_ref = vm_ref or self._get_vm_opaque_ref(instance)
LOG.debug("Starting instance", instance=instance)
@@ -339,7 +341,7 @@ class VMOps(object):
self._session.call_xenapi('VM.start_on', vm_ref,
self._session.host_ref,
- False, False)
+ start_pause, False)
# Allow higher-layers a chance to detach bad-volumes as well (in order
# to cleanup BDM entries and detach in Cinder)
@@ -548,14 +550,13 @@ class VMOps(object):
self._prepare_instance_filter(instance, network_info)
@step
- def boot_instance_step(undo_mgr, vm_ref):
+ def start_paused_instance_step(undo_mgr, vm_ref):
if power_on:
- self._start(instance, vm_ref)
- self._wait_for_instance_to_start(instance, vm_ref)
- self._update_last_dom_id(vm_ref)
+ self._start(instance, vm_ref, start_pause=True)
@step
- def configure_booted_instance_step(undo_mgr, vm_ref):
+ def boot_and_configure_instance_step(undo_mgr, vm_ref):
+ self._update_instance_with_power_on(vm_ref, instance, power_on)
if first_boot:
self._configure_new_instance_with_agent(instance, vm_ref,
injected_files, admin_password)
@@ -583,22 +584,61 @@ class VMOps(object):
attach_devices_step(undo_mgr, vm_ref, vdis, disk_image_type)
inject_instance_data_step(undo_mgr, vm_ref, vdis)
- setup_network_step(undo_mgr, vm_ref)
- if rescue:
- attach_orig_disks_step(undo_mgr, vm_ref)
-
- boot_instance_step(undo_mgr, vm_ref)
+ # if use neutron, prepare waiting event from neutron
+ timeout = CONF.vif_plugging_timeout
+ events = self._get_neutron_events(network_info,
+ power_on, first_boot)
+ try:
+ with self._virtapi.wait_for_instance_event(
+ instance, events, deadline=timeout,
+ error_callback=self._neutron_failed_callback):
+ LOG.debug("wait for instance event:%s", events)
+ setup_network_step(undo_mgr, vm_ref)
+ if rescue:
+ attach_orig_disks_step(undo_mgr, vm_ref)
+ start_paused_instance_step(undo_mgr, vm_ref)
+ except eventlet.timeout.Timeout:
+ self._handle_neutron_event_timeout(instance, undo_mgr)
- configure_booted_instance_step(undo_mgr, vm_ref)
apply_security_group_filters_step(undo_mgr)
-
+ boot_and_configure_instance_step(undo_mgr, vm_ref)
if completed_callback:
completed_callback()
except Exception:
msg = _("Failed to spawn, rolling back")
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
+ def _handle_neutron_event_timeout(self, instance, undo_mgr):
+ # We didn't get callback from Neutron within given time
+ LOG.warn(_LW('Timeout waiting for vif plugging callback'),
+ instance=instance)
+ if CONF.vif_plugging_is_fatal:
+ raise exception.VirtualInterfaceCreateException()
+
+ def _update_instance_with_power_on(self, vm_ref, instance, power_on):
+ if power_on:
+ LOG.debug("Update instance when power on", instance=instance)
+ self._session.VM.unpause(vm_ref)
+ self._wait_for_instance_to_start(instance, vm_ref)
+ self._update_last_dom_id(vm_ref)
+
+ def _neutron_failed_callback(self, event_name, instance):
+ LOG.warn(_LW('Neutron Reported failure on event %(event)s'),
+ {'event': event_name}, instance=instance)
+ if CONF.vif_plugging_is_fatal:
+ raise exception.VirtualInterfaceCreateException()
+
+ def _get_neutron_events(self, network_info, power_on, first_boot):
+ # Only get network-vif-plugged events with VIF's status is not active.
+ # With VIF whose status is active, neutron may not notify such event.
+ timeout = CONF.vif_plugging_timeout
+ if (utils.is_neutron() and power_on and timeout and first_boot):
+ return [('network-vif-plugged', vif['id'])
+ for vif in network_info if vif.get('active', True) is False]
+ else:
+ return []
+
def _attach_orig_disks(self, instance, vm_ref):
orig_vm_ref = vm_utils.lookup(self._session, instance['name'])
orig_vdi_refs = self._find_vdi_refs(orig_vm_ref,

View File

@ -0,0 +1,715 @@
diff --git a/nova/tests/unit/virt/xenapi/stubs.py b/nova/tests/unit/virt/xenapi/stubs.py
index 5d515b1..f3a49b2 100644
--- a/nova/tests/unit/virt/xenapi/stubs.py
+++ b/nova/tests/unit/virt/xenapi/stubs.py
@@ -29,6 +29,13 @@ from nova.virt.xenapi import vm_utils
from nova.virt.xenapi import vmops
+def stubout_create_vifs(stubs, vmops):
+ def fake_create_vifs(*args):
+ return ['fake_vif_ref']
+
+ stubs.Set(vmops, '_create_vifs', fake_create_vifs)
+
+
def stubout_firewall_driver(stubs, conn):
def fake_none(self, *args):
diff --git a/nova/tests/unit/virt/xenapi/test_vif.py b/nova/tests/unit/virt/xenapi/test_vif.py
index a41e506..65e3070 100644
--- a/nova/tests/unit/virt/xenapi/test_vif.py
+++ b/nova/tests/unit/virt/xenapi/test_vif.py
@@ -13,7 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
+from mock import call
+from mock import Mock
+from mock import patch
from nova import exception
from nova.network import model
@@ -30,7 +32,7 @@ fake_vif = {
'address': '00:00:00:00:00:00',
'network_id': 123,
'instance_uuid': 'fake-uuid',
- 'uuid': 'fake-uuid-2',
+ 'uuid': 'fake_uuid',
}
@@ -42,7 +44,7 @@ def fake_call_xenapi(method, *args):
return {'uuid': fake_vif['uuid'],
'MAC': fake_vif['address'],
'network': 'fake_network',
- 'other_config': {'nicira-iface-id': fake_vif['id']}
+ 'other_config': {'nicira-iface-id': 'fake-nicira-iface-id'}
}
else:
raise exception.Exception("Failed get vif record")
@@ -58,14 +60,27 @@ def fake_call_xenapi(method, *args):
return "fake_vif_ref"
else:
raise exception.Exception("VIF existed")
+ if method == "network.create":
+ return "fake_network_ref"
+ if method == "network.get_bridge":
+ return "fake_bridge"
+ if method == "network.get_VIFs":
+ return
+ if method == "network.destroy":
+ return
return "Unexpected call_xenapi: %s.%s" % (method, args)
+def fake_call_plugin(plugin, method, *args):
+ return
+
+
class XenVIFDriverTestBase(stubs.XenAPITestBaseNoDB):
def setUp(self):
super(XenVIFDriverTestBase, self).setUp()
- self._session = mock.Mock()
+ self._session = Mock()
self._session.call_xenapi.side_effect = fake_call_xenapi
+ self._session.call_plugin.side_effect = fake_call_plugin
class XenVIFDriverTestCase(XenVIFDriverTestBase):
@@ -79,8 +94,8 @@ class XenVIFDriverTestCase(XenVIFDriverTestBase):
ret_vif_ref = self.base_driver._get_vif_ref(fake_vif, vm_ref)
self.assertEqual(vif_ref, ret_vif_ref)
- expected = [mock.call('VM.get_VIFs', vm_ref),
- mock.call('VIF.get_record', vif_ref)]
+ expected = [call('VM.get_VIFs', vm_ref),
+ call('VIF.get_record', vif_ref)]
self.assertEqual(expected, self._session.call_xenapi.call_args_list)
def test_get_vif_ref_none_and_exception(self):
@@ -89,9 +104,9 @@ class XenVIFDriverTestCase(XenVIFDriverTestBase):
ret_vif_ref = self.base_driver._get_vif_ref(vif, vm_ref)
self.assertIsNone(ret_vif_ref)
- expected = [mock.call('VM.get_VIFs', vm_ref),
- mock.call('VIF.get_record', 'fake_vif_ref'),
- mock.call('VIF.get_record', 'fake_vif_ref_A2')]
+ expected = [call('VM.get_VIFs', vm_ref),
+ call('VIF.get_record', 'fake_vif_ref'),
+ call('VIF.get_record', 'fake_vif_ref_A2')]
self.assertEqual(expected, self._session.call_xenapi.call_args_list)
def test_create_vif(self):
@@ -100,7 +115,7 @@ class XenVIFDriverTestCase(XenVIFDriverTestBase):
ret_vif_ref = self.base_driver._create_vif(fake_vif, vif_rec, vm_ref)
self.assertEqual("fake_vif_ref", ret_vif_ref)
- expected = [mock.call('VIF.create', vif_rec)]
+ expected = [call('VIF.create', vif_rec)]
self.assertEqual(expected, self._session.call_xenapi.call_args_list)
def test_create_vif_exception(self):
@@ -108,16 +123,16 @@ class XenVIFDriverTestCase(XenVIFDriverTestBase):
self.base_driver._create_vif,
"fake_vif", "missing_vif_rec", "fake_vm_ref")
- @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ @patch.object(vif.XenVIFDriver, '_get_vif_ref',
return_value='fake_vif_ref')
def test_unplug(self, mock_get_vif_ref):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
self.base_driver.unplug(instance, fake_vif, vm_ref)
- expected = [mock.call('VIF.destroy', 'fake_vif_ref')]
+ expected = [call('VIF.destroy', 'fake_vif_ref')]
self.assertEqual(expected, self._session.call_xenapi.call_args_list)
- @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ @patch.object(vif.XenVIFDriver, '_get_vif_ref',
return_value='missing_vif_ref')
def test_unplug_exception(self, mock_get_vif_ref):
instance = "fake_instance"
@@ -132,9 +147,9 @@ class XenAPIBridgeDriverTestCase(XenVIFDriverTestBase, object):
super(XenAPIBridgeDriverTestCase, self).setUp()
self.bridge_driver = vif.XenAPIBridgeDriver(self._session)
- @mock.patch.object(vif.XenAPIBridgeDriver, '_ensure_vlan_bridge',
+ @patch.object(vif.XenAPIBridgeDriver, '_ensure_vlan_bridge',
return_value='fake_network_ref')
- @mock.patch.object(vif.XenVIFDriver, '_create_vif',
+ @patch.object(vif.XenVIFDriver, '_create_vif',
return_value='fake_vif_ref')
def test_plug_create_vlan(self, mock_create_vif, mock_ensure_vlan_bridge):
instance = {'name': "fake_instance_name"}
@@ -149,14 +164,14 @@ class XenAPIBridgeDriverTestCase(XenVIFDriverTestBase, object):
ret_vif_ref = self.bridge_driver.plug(instance, vif, vm_ref, device)
self.assertEqual('fake_vif_ref', ret_vif_ref)
- @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ @patch.object(vif.XenVIFDriver, '_get_vif_ref',
return_value='fake_vif_ref')
def test_unplug(self, mock_get_vif_ref):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
self.bridge_driver.unplug(instance, fake_vif, vm_ref)
- expected = [mock.call('VIF.destroy', 'fake_vif_ref')]
+ expected = [call('VIF.destroy', 'fake_vif_ref')]
self.assertEqual(expected, self._session.call_xenapi.call_args_list)
@@ -165,25 +180,106 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
super(XenAPIOpenVswitchDriverTestCase, self).setUp()
self.ovs_driver = vif.XenAPIOpenVswitchDriver(self._session)
- @mock.patch.object(network_utils, 'find_network_with_bridge',
- return_value='fake_network_ref')
- @mock.patch.object(vif.XenVIFDriver, '_create_vif',
+ @patch.object(vif.XenVIFDriver, '_create_vif',
return_value='fake_vif_ref')
- @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref', return_value=None)
- def test_plug(self, mock_get_vif_ref, mock_create_vif,
- mock_find_network_with_bridge):
+ @patch.object(vif.XenAPIOpenVswitchDriver, 'create_vif_interim_network')
+ @patch.object(vif.XenVIFDriver, '_get_vif_ref', return_value=None)
+ def test_plug(self, mock_get_vif_ref,
+ mock_create_vif_interim_network,
+ mock_create_vif):
instance = {'name': "fake_instance_name"}
vm_ref = "fake_vm_ref"
device = 1
ret_vif_ref = self.ovs_driver.plug(instance, fake_vif, vm_ref, device)
self.assertEqual('fake_vif_ref', ret_vif_ref)
+ self.assertTrue(mock_create_vif_interim_network.called)
- @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ @patch.object(vif.XenAPIOpenVswitchDriver, 'get_vif_interim_net_name',
+ return_value="fake_net_name")
+ @patch.object(vif.XenAPIOpenVswitchDriver, '_get_patch_port_pair_names',
+ return_value=("fake_port_name1", "fake_port_name2"))
+ @patch.object(network_utils, 'find_network_with_name_label',
+ return_value='fake_network')
+ @patch.object(vif.XenVIFDriver, '_get_vif_ref',
return_value='fake_vif_ref')
- def test_unplug(self, mock_get_vif_ref):
+ def test_unplug(self, mock_get_vif_ref,
+ mock_find_network_with_name_label,
+ mock_get_patch_port_pair_names,
+ mock_get_vif_interim_net_name):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
self.ovs_driver.unplug(instance, fake_vif, vm_ref)
- expected = [mock.call('VIF.destroy', 'fake_vif_ref')]
- self.assertEqual(expected, self._session.call_xenapi.call_args_list)
+ del_p1 = '["ovs-vsctl", "--", "--if-exists", ' \
+ '"del-port", "fake_bridge", "fake_port_name1"]'
+ del_p2 = '["ovs-vsctl", "--", "--if-exists", ' \
+ '"del-port", "xapi1", "fake_port_name2"]'
+ del_br = '["ovs-vsctl", "--", "--if-exists", ' \
+ '"del-br", "fake_bridge"]'
+
+ expected_calls = [call.call_xenapi('VIF.destroy', 'fake_vif_ref'),
+ call.call_xenapi('network.get_VIFs', 'fake_network'),
+ call.call_xenapi('network.get_bridge', 'fake_network'),
+ call.call_plugin('xenhost', 'network_config', {'cmd': del_p1}),
+ call.call_plugin('xenhost', 'network_config', {'cmd': del_p2}),
+ call.call_xenapi('network.destroy', 'fake_network'),
+ call.call_plugin('xenhost', 'network_config', {'cmd': del_br})]
+ self._session.assert_has_calls(expected_calls)
+
+ @patch.object(vif.XenAPIOpenVswitchDriver, 'get_vif_interim_net_name',
+ return_value="fake_net_name")
+ @patch.object(vif.XenAPIOpenVswitchDriver, '_get_patch_port_pair_names',
+ return_value=("fake_port_name1", "fake_port_name2"))
+ def test_post_start_actions(self, mock_get_patch_port_pair_names,
+ mock_get_vif_interim_net_name):
+ vif_ref = "fake_vif_ref"
+ instance = {'name': 'fake_instance_name'}
+ self.ovs_driver.post_start_actions(instance, vif_ref)
+
+ add_p1 = '["ovs-vsctl", "--", ' \
+ '"--if-exists", "del-port", "fake_port_name1", "--", ' \
+ '"add-port", "fake_bridge", "fake_port_name1", "--", ' \
+ '"set", "interface", "fake_port_name1", ' \
+ '"type=patch", "options:peer=fake_port_name2"]'
+ add_p2 = '["ovs-vsctl", "--", ' \
+ '"--if-exists", "del-port", "fake_port_name2", "--", ' \
+ '"add-port", "xapi1", "fake_port_name2", "--", ' \
+ '"set", "interface", "fake_port_name2", ' \
+ '"type=patch", "options:peer=fake_port_name1"]'
+ ext_map = '["ovs-vsctl", "set", "Interface", "fake_port_name2", ' \
+ '"external-ids:attached-mac=00:00:00:00:00:00", ' \
+ '"external-ids:iface-id=fake-nicira-iface-id", ' \
+ '"external-ids:iface-status=active", ' \
+ '"external-ids:xs-vif-uuid=fake_uuid"]'
+
+ expected_calls = [call.call_xenapi('VIF.get_record', vif_ref),
+ call.call_xenapi('network.get_bridge', 'fake_network'),
+ call.call_plugin('xenhost', 'network_config', {'cmd': add_p1}),
+ call.call_plugin('xenhost', 'network_config', {'cmd': add_p2}),
+ call.call_plugin('xenhost', 'network_config', {'cmd': ext_map})]
+ self._session.assert_has_calls(expected_calls)
+
+ @patch.object(network_utils, 'find_network_with_name_label',
+ return_value="fake_network_ref")
+ @patch.object(vif.XenAPIOpenVswitchDriver, 'get_vif_interim_net_name',
+ return_value="fake_net_name")
+ def test_create_vif_interim_network_exist(self,
+ mock_get_vif_interim_net_name,
+ mock_find_network_with_name_label):
+ self.ovs_driver.create_vif_interim_network(fake_vif)
+ self._session.call_xenapi.assert_not_called()
+
+ @patch.object(network_utils, 'find_network_with_name_label',
+ return_value=None)
+ @patch.object(vif.XenAPIOpenVswitchDriver, 'get_vif_interim_net_name',
+ return_value="fake_net_name")
+ def test_create_vif_interim_network_new(self,
+ mock_get_vif_interim_net_name,
+ mock_find_network_with_name_label):
+ network_rec = {'name_label': "fake_net_name",
+ 'name_description': "interim network for vif",
+ 'other_config': {}}
+ network_ref = self.ovs_driver.create_vif_interim_network(fake_vif)
+ self._session.call_xenapi.assert_called_once_with('network.create',
+ network_rec)
+ self.assertEqual(network_ref, 'fake_network_ref')
diff --git a/nova/tests/unit/virt/xenapi/test_vmops.py b/nova/tests/unit/virt/xenapi/test_vmops.py
index 33bec73..c6f1499 100644
--- a/nova/tests/unit/virt/xenapi/test_vmops.py
+++ b/nova/tests/unit/virt/xenapi/test_vmops.py
@@ -50,6 +50,7 @@ class VMOpsTestBase(stubs.XenAPITestBaseNoDB):
self._session = xenapi_session.XenAPISession('test_url', 'root',
'test_pass')
self.vmops = vmops.VMOps(self._session, fake.FakeVirtAPI())
+ stubs.stubout_create_vifs(self.stubs, self.vmops)
def create_vm(self, name, state="Running"):
vm_ref = xenapi_fake.create_vm(name, state)
@@ -303,7 +304,6 @@ class SpawnTestCase(VMOpsTestBase):
self.mox.StubOutWithMock(self.vmops, '_inject_instance_metadata')
self.mox.StubOutWithMock(self.vmops, '_inject_auto_disk_config')
self.mox.StubOutWithMock(self.vmops, '_file_inject_vm_settings')
- self.mox.StubOutWithMock(self.vmops, '_create_vifs')
self.mox.StubOutWithMock(self.vmops.firewall_driver,
'setup_basic_filtering')
self.mox.StubOutWithMock(self.vmops.firewall_driver,
@@ -401,6 +401,9 @@ class SpawnTestCase(VMOpsTestBase):
"0/0000:00:00.0")
else:
pci_manager.get_instance_pci_devs(instance).AndReturn([])
+
+ self.vmops._create_vifs(instance, vm_ref, network_info)
+
step += 1
self.vmops._update_instance_progress(context, instance, step, steps)
@@ -425,15 +428,6 @@ class SpawnTestCase(VMOpsTestBase):
error_callback=self.vmops._neutron_failed_callback).\
AndRaise(exception.VirtualInterfaceCreateException)
else:
- self.vmops._create_vifs(instance, vm_ref, network_info)
- self.vmops.firewall_driver.setup_basic_filtering(instance,
- network_info).AndRaise(NotImplementedError)
- self.vmops.firewall_driver.prepare_instance_filter(instance,
- network_info)
- step += 1
- self.vmops._update_instance_progress(context, instance,
- step, steps)
-
if rescue:
self.vmops._attach_orig_disks(instance, vm_ref)
step += 1
@@ -444,6 +438,22 @@ class SpawnTestCase(VMOpsTestBase):
step += 1
self.vmops._update_instance_progress(context, instance,
step, steps)
+
+ self.vmops.firewall_driver.setup_basic_filtering(instance,
+ network_info).AndRaise(NotImplementedError)
+ self.vmops.firewall_driver.prepare_instance_filter(instance,
+ network_info)
+ step += 1
+ self.vmops._update_instance_progress(context, instance, step,
+ steps)
+
+ self.vmops._configure_new_instance_with_agent(instance, vm_ref,
+ injected_files, admin_password)
+ self.vmops._remove_hostname(instance, vm_ref)
+ step += 1
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+
self.vmops.firewall_driver.apply_instance_filter(instance,
network_info)
step += 1
@@ -560,6 +570,7 @@ class SpawnTestCase(VMOpsTestBase):
vdis, di_type, network_info, False, None, None)
self.vmops._attach_mapped_block_devices(instance, block_device_info)
pci_manager.get_instance_pci_devs(instance).AndReturn([])
+ self.vmops._create_vifs(instance, vm_ref, network_info)
self.vmops._inject_instance_metadata(instance, vm_ref)
self.vmops._inject_auto_disk_config(instance, vm_ref)
@@ -567,15 +578,14 @@ class SpawnTestCase(VMOpsTestBase):
network_info)
self.vmops.inject_network_info(instance, network_info, vm_ref)
- self.vmops._create_vifs(instance, vm_ref, network_info)
+ if power_on:
+ self.vmops._start(instance, vm_ref, start_pause=True)
+
self.vmops.firewall_driver.setup_basic_filtering(instance,
network_info).AndRaise(NotImplementedError)
self.vmops.firewall_driver.prepare_instance_filter(instance,
network_info)
- if power_on:
- self.vmops._start(instance, vm_ref, start_pause=True)
-
self.vmops.firewall_driver.apply_instance_filter(instance,
network_info)
if power_on:
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py
index 5c7a350..5c1ac29 100644
--- a/nova/virt/xenapi/vif.py
+++ b/nova/virt/xenapi/vif.py
@@ -19,13 +19,17 @@
from oslo_config import cfg
from oslo_log import log as logging
+from oslo_serialization import jsonutils
from nova import exception
from nova.i18n import _
from nova.i18n import _LW
+from nova.network import model as network_model
from nova.virt.xenapi import network_utils
from nova.virt.xenapi import vm_utils
+LOG = logging.getLogger(__name__)
+
xenapi_ovs_integration_bridge_opt = cfg.StrOpt('ovs_integration_bridge',
default='xapi1',
@@ -63,8 +67,6 @@ class XenVIFDriver(object):
raise exception.NovaException(
reason=_("Failed to create vif %s") % vif)
- LOG.debug("create vif %(vif)s for vm %(vm_ref)s successfully",
- {'vif': vif, 'vm_ref': vm_ref})
return vif_ref
def unplug(self, instance, vif, vm_ref):
@@ -185,11 +187,18 @@ class XenAPIBridgeDriver(XenVIFDriver):
def unplug(self, instance, vif, vm_ref):
super(XenAPIBridgeDriver, self).unplug(instance, vif, vm_ref)
+ def post_start_actions(self, instance, vif_ref):
+ """no further actions needed for this driver type"""
+ pass
+
class XenAPIOpenVswitchDriver(XenVIFDriver):
"""VIF driver for Open vSwitch with XenAPI."""
def plug(self, instance, vif, vm_ref=None, device=None):
+ """create an interim network for this vif; and build
+ the vif_rec which will be used by xapi to create VM vif
+ """
if not vm_ref:
vm_ref = vm_utils.lookup(self._session, instance['name'])
@@ -203,10 +212,9 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
if not device:
device = 0
- # with OVS model, always plug into an OVS integration bridge
- # that is already created
- network_ref = network_utils.find_network_with_bridge(
- self._session, CONF.xenserver.ovs_integration_bridge)
+ # create an interim network which has a bridge directly connected
+ # to instance
+ network_ref = self.create_vif_interim_network(vif)
vif_rec = {}
vif_rec['device'] = str(device)
vif_rec['network'] = network_ref
@@ -221,4 +229,138 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
return self._create_vif(vif, vif_rec, vm_ref)
def unplug(self, instance, vif, vm_ref):
- super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref)
+ """unplug vif:
+ 1. unplug and destroy vif.
+ 2. delete the patch port pair between the integration bridge and
+ the interim network.
+ 3. destroy the interim network
+ 4. delete the OVS bridge service for the interim network
+ """
+ try:
+ super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref)
+
+ net_name = self.get_vif_interim_net_name(vif)
+ network = network_utils.find_network_with_name_label(
+ self._session, net_name)
+ if network is None:
+ return
+ vifs = self._session.call_xenapi('network.get_VIFs', network)
+ if vifs:
+ # only remove the interim network when it's empty.
+ # for resize/migrate on local host, vifs on both of the
+ # source and target VM will be connected to the same
+ # interim network.
+ return
+ LOG.debug('destroying patch port pair for vif: vif_id=%(vif_id)s',
+ {'vif_id': vif['id']})
+ bridge_name = self._session.call_xenapi('network.get_bridge',
+ network)
+ patch_port1, patch_port2 = self._get_patch_port_pair_names(
+ vif['id'])
+ # delete the patch port pair
+ self._del_ovs_port(bridge_name, patch_port1)
+ self._del_ovs_port(CONF.xenserver.ovs_integration_bridge,
+ patch_port2)
+
+ LOG.debug('destroying network: network=%(network)s,'
+ 'bridge=%(br)s',
+ {'network': network, 'br': bridge_name})
+ self._session.call_xenapi('network.destroy', network)
+ # delete bridge if it still exists.
+ # As there is patch port existing on this bridge when destroying
+ # the VM vif (which happens when shutdown the VM), the bridge
+ # won't be destroyed automatically by XAPI. So let's destroy it
+ # at here.
+ self._del_ovs_br(bridge_name)
+ except Exception as e:
+ LOG.debug("Fail to unplug vif %(vif)s, exception:%(exception)s",
+ {'vif': vif, 'exception': e}, instance=instance)
+
+ def post_start_actions(self, instance, vif_ref):
+ """Do needed actions post vif start:
+ plug the interim ovs bridge to the integration bridge;
+ set external_ids to the int-br port which will service
+ for this vif.
+ """
+ vif_rec = self._session.call_xenapi('VIF.get_record', vif_ref)
+ network_ref = vif_rec['network']
+ bridge_name = self._session.call_xenapi('network.get_bridge',
+ network_ref)
+ iface_id = vif_rec['other_config']['nicira-iface-id']
+ patch_port1, patch_port2 = self._get_patch_port_pair_names(iface_id)
+ LOG.debug('plug_ovs_bridge: port1=%(port1)s, port2=%(port2)s,'
+ 'network_ref=%(network_ref)s, bridge_name=%(bridge_name)s',
+ {'port1': patch_port1, 'port2': patch_port2,
+ 'network_ref': network_ref,
+ 'bridge_name': bridge_name})
+ if bridge_name is None:
+ raise Exception(_("Can't find bridge for vif_ref:%(vif_ref)s of"
+ "VM:%(vm_name)s"),
+ {'vif_ref': vif_ref, 'vm_name': instance['name']})
+
+ self._add_patch_port(bridge_name, patch_port1, patch_port2)
+ self._add_patch_port(CONF.xenserver.ovs_integration_bridge,
+ patch_port2, patch_port1)
+ self._map_external_ids_with_vif(patch_port2, vif_rec)
+
+ def get_vif_interim_net_name(self, vif):
+ return ("net-" + vif['id'])[:network_model.NIC_NAME_LEN]
+
+ def create_vif_interim_network(self, vif):
+ net_name = self.get_vif_interim_net_name(vif)
+ network_rec = {'name_label': net_name,
+ 'name_description': "interim network for vif",
+ 'other_config': {}}
+ network_ref = network_utils.find_network_with_name_label(
+ self._session, net_name)
+ if network_ref:
+ # already exist, just return
+ # in some scenarios: e..g resize/migrate, it won't create new
+ # interim network.
+ return network_ref
+ network_ref = self._session.call_xenapi('network.create', network_rec)
+ if network_ref is None:
+ raise Exception(_("Failed to create the interim netowrk for vif -"
+ "%(vif_id)s"),
+ {'vif_id': vif['id']})
+ return network_ref
+
+ def _get_patch_port_pair_names(self, iface_id):
+ return (("pp1-%s" % iface_id)[:network_model.NIC_NAME_LEN],
+ ("pp2-%s" % iface_id)[:network_model.NIC_NAME_LEN])
+
+ def _add_patch_port(self, bridge_name, port_name, peer_port_name):
+ cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port_name,
+ '--', 'add-port', bridge_name, port_name,
+ '--', 'set', 'interface', port_name,
+ 'type=patch', 'options:peer=%s' % peer_port_name]
+ self._exec_dom0_cmd(cmd_args)
+
+ def _del_ovs_port(self, bridge_name, port_name):
+ cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port',
+ bridge_name, port_name]
+ self._exec_dom0_cmd(cmd_args)
+
+ def _del_ovs_br(self, bridge_name):
+ cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-br', bridge_name]
+ self._exec_dom0_cmd(cmd_args)
+
+ def _map_external_ids_with_vif(self, interface, vif_rec):
+ '''set external ids on the integration bridge vif
+ '''
+ mac = vif_rec['MAC']
+ iface_id = vif_rec['other_config']['nicira-iface-id']
+ vif_uuid = vif_rec['uuid']
+ cmd_args = ['ovs-vsctl', 'set', 'Interface', interface,
+ 'external-ids:attached-mac=%s' % mac,
+ 'external-ids:iface-id=%s' % iface_id,
+ 'external-ids:iface-status=active',
+ 'external-ids:xs-vif-uuid=%s' % vif_uuid,
+ ]
+ self._exec_dom0_cmd(cmd_args)
+
+ def _exec_dom0_cmd(self, cmd_args):
+ args = {
+ 'cmd': jsonutils.dumps(cmd_args),
+ }
+ self._session.call_plugin('xenhost', 'network_config', args)
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index c8fdc52..0a48a3f 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1686,6 +1686,15 @@ def lookup_vm_vdis(session, vm_ref):
return vdi_refs
+def lookup_vm_vifs(session, vm_ref):
+ """Look for the VIFs that are attached to the VM."""
+ try:
+ vif_refs = session.call_xenapi("VM.get_VIFs", vm_ref)
+ except session.XenAPI.Failure:
+ LOG.exception(_LE('"Look for the VIFs failed'))
+ return vif_refs
+
+
def lookup(session, name_label, check_rescue=False):
"""Look the instance up and return it if available.
:param:check_rescue: if True will return the 'name'-rescue vm if it
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 1fbc15a..0e4509f 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -348,6 +348,15 @@ class VMOps(object):
if bad_volumes_callback and bad_devices:
bad_volumes_callback(bad_devices)
+ # Do some post start operations which needed to be done after start.
+ self._post_start_actions(instance)
+
+ def _post_start_actions(self, instance):
+ vm_ref = vm_utils.lookup(self._session, instance['name'])
+ vif_refs = vm_utils.lookup_vm_vifs(self._session, vm_ref)
+ for vif_ref in vif_refs:
+ self.vif_driver.post_start_actions(instance, vif_ref)
+
def _get_vdis_for_instance(self, context, instance, name_label,
image_meta, image_type, block_device_info):
"""Create or connect to all virtual disks for this instance."""
@@ -513,9 +522,11 @@ class VMOps(object):
return vm_ref
@step
- def attach_devices_step(undo_mgr, vm_ref, vdis, disk_image_type):
+ def attach_devices_step(undo_mgr, instance, vm_ref, vdis,
+ disk_image_type, network_info):
attach_disks(undo_mgr, vm_ref, vdis, disk_image_type)
attach_pci_devices(undo_mgr, vm_ref)
+ self._create_vifs(instance, vm_ref, network_info)
if rescue:
# NOTE(johannes): Attach disks from original VM to rescue VM now,
@@ -581,7 +592,8 @@ class VMOps(object):
vm_ref = create_vm_record_step(undo_mgr, disk_image_type,
kernel_file, ramdisk_file)
- attach_devices_step(undo_mgr, vm_ref, vdis, disk_image_type)
+ attach_devices_step(undo_mgr, instance, vm_ref, vdis,
+ disk_image_type, network_info)
inject_instance_data_step(undo_mgr, vm_ref, vdis)
@@ -1930,13 +1942,20 @@ class VMOps(object):
"""Creates vifs for an instance."""
LOG.debug("Creating vifs", instance=instance)
+ vif_refs = []
# this function raises if vm_ref is not a vm_opaque_ref
self._session.call_xenapi("VM.get_domid", vm_ref)
for device, vif in enumerate(network_info):
LOG.debug('Create VIF %s', vif, instance=instance)
- self.vif_driver.plug(instance, vif, vm_ref=vm_ref, device=device)
+ vif_ref = self.vif_driver.plug(instance, vif,
+ vm_ref=vm_ref, device=device)
+ vif_refs.append(vif_ref)
+
+ LOG.debug('Created the vif_refs: %(vifs)s for VM name: %(name)s',
+ {'vifs': vif_refs, 'name': instance['name']},
+ instance=instance)
def plug_vifs(self, instance, network_info):
"""Set up VIF networking on the host."""
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version
index b67c84b..8674d0a 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version
@@ -28,7 +28,13 @@ import utils
# 1.0 - Initial version.
# 1.1 - New call to check GC status
# 1.2 - Added support for pci passthrough devices
+<<<<<<< HEAD
PLUGIN_VERSION = "1.2"
+=======
+# 1.3 - Add vhd2 functions for doing glance operations by url
+# 1.4 - Added function for network configuration on ovs bridge
+PLUGIN_VERSION = "1.4"
+>>>>>>> 52d0d42... xenapi: OVS agent updates the wrong port when using XenServer + Neutron
def get_version(session):
return PLUGIN_VERSION
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
index 0e95e33..4bf85ac 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
@@ -213,6 +213,22 @@ def iptables_config(session, args):
raise pluginlib.PluginError(_("Invalid iptables command"))
+def network_config(session, args):
+ # function to config OVS bridge
+ ALLOWED_CMDS = [
+ 'ovs-vsctl',
+ ]
+ cmd = json.loads(args.get('cmd'))
+ if cmd is None or cmd == []:
+ msg = _("empty command is supplied")
+ raise pluginlib.PluginError(msg)
+ if cmd[0] not in ALLOWED_CMDS:
+ msg = _("Dom0 execution of '%s' is not permitted") % cmd[0]
+ raise pluginlib.PluginError(msg)
+ result = _run_command(cmd, json.loads(args.get('cmd_input', 'null')))
+ return json.dumps(result)
+
+
def _power_action(action, arg_dict):
# Host must be disabled first
host_uuid = arg_dict['host_uuid']
@@ -457,4 +473,5 @@ if __name__ == "__main__":
"get_config": get_config,
"set_config": set_config,
"iptables_config": iptables_config,
+ "network_config": network_config,
"host_uptime": host_uptime})

View File

@ -0,0 +1,376 @@
diff --git a/nova/tests/unit/virt/xenapi/test_vif.py b/nova/tests/unit/virt/xenapi/test_vif.py
new file mode 100644
index 0000000..a41e506
--- /dev/null
+++ b/nova/tests/unit/virt/xenapi/test_vif.py
@@ -0,0 +1,189 @@
+# Copyright 2013 OpenStack Foundation
+# 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.
+
+import mock
+
+from nova import exception
+from nova.network import model
+from nova.tests.unit.virt.xenapi import stubs
+from nova.virt.xenapi import network_utils
+from nova.virt.xenapi import vif
+
+fake_vif = {
+ 'created_at': None,
+ 'updated_at': None,
+ 'deleted_at': None,
+ 'deleted': 0,
+ 'id': '123456789123',
+ 'address': '00:00:00:00:00:00',
+ 'network_id': 123,
+ 'instance_uuid': 'fake-uuid',
+ 'uuid': 'fake-uuid-2',
+}
+
+
+def fake_call_xenapi(method, *args):
+ if method == "VM.get_VIFs":
+ return ["fake_vif_ref", "fake_vif_ref_A2"]
+ if method == "VIF.get_record":
+ if args[0] == "fake_vif_ref":
+ return {'uuid': fake_vif['uuid'],
+ 'MAC': fake_vif['address'],
+ 'network': 'fake_network',
+ 'other_config': {'nicira-iface-id': fake_vif['id']}
+ }
+ else:
+ raise exception.Exception("Failed get vif record")
+ if method == "VIF.unplug":
+ return
+ if method == "VIF.destroy":
+ if args[0] == "fake_vif_ref":
+ return
+ else:
+ raise exception.Exception("unplug vif failed")
+ if method == "VIF.create":
+ if args[0] == "fake_vif_rec":
+ return "fake_vif_ref"
+ else:
+ raise exception.Exception("VIF existed")
+ return "Unexpected call_xenapi: %s.%s" % (method, args)
+
+
+class XenVIFDriverTestBase(stubs.XenAPITestBaseNoDB):
+ def setUp(self):
+ super(XenVIFDriverTestBase, self).setUp()
+ self._session = mock.Mock()
+ self._session.call_xenapi.side_effect = fake_call_xenapi
+
+
+class XenVIFDriverTestCase(XenVIFDriverTestBase):
+ def setUp(self):
+ super(XenVIFDriverTestCase, self).setUp()
+ self.base_driver = vif.XenVIFDriver(self._session)
+
+ def test_get_vif_ref(self):
+ vm_ref = "fake_vm_ref"
+ vif_ref = 'fake_vif_ref'
+ ret_vif_ref = self.base_driver._get_vif_ref(fake_vif, vm_ref)
+ self.assertEqual(vif_ref, ret_vif_ref)
+
+ expected = [mock.call('VM.get_VIFs', vm_ref),
+ mock.call('VIF.get_record', vif_ref)]
+ self.assertEqual(expected, self._session.call_xenapi.call_args_list)
+
+ def test_get_vif_ref_none_and_exception(self):
+ vm_ref = "fake_vm_ref"
+ vif = {'address': "no_match_vif_address"}
+ ret_vif_ref = self.base_driver._get_vif_ref(vif, vm_ref)
+ self.assertIsNone(ret_vif_ref)
+
+ expected = [mock.call('VM.get_VIFs', vm_ref),
+ mock.call('VIF.get_record', 'fake_vif_ref'),
+ mock.call('VIF.get_record', 'fake_vif_ref_A2')]
+ self.assertEqual(expected, self._session.call_xenapi.call_args_list)
+
+ def test_create_vif(self):
+ vif_rec = "fake_vif_rec"
+ vm_ref = "fake_vm_ref"
+ ret_vif_ref = self.base_driver._create_vif(fake_vif, vif_rec, vm_ref)
+ self.assertEqual("fake_vif_ref", ret_vif_ref)
+
+ expected = [mock.call('VIF.create', vif_rec)]
+ self.assertEqual(expected, self._session.call_xenapi.call_args_list)
+
+ def test_create_vif_exception(self):
+ self.assertRaises(exception.NovaException,
+ self.base_driver._create_vif,
+ "fake_vif", "missing_vif_rec", "fake_vm_ref")
+
+ @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ return_value='fake_vif_ref')
+ def test_unplug(self, mock_get_vif_ref):
+ instance = {'name': "fake_instance"}
+ vm_ref = "fake_vm_ref"
+ self.base_driver.unplug(instance, fake_vif, vm_ref)
+ expected = [mock.call('VIF.destroy', 'fake_vif_ref')]
+ self.assertEqual(expected, self._session.call_xenapi.call_args_list)
+
+ @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ return_value='missing_vif_ref')
+ def test_unplug_exception(self, mock_get_vif_ref):
+ instance = "fake_instance"
+ vm_ref = "fake_vm_ref"
+ self.assertRaises(exception.NovaException,
+ self.base_driver.unplug,
+ instance, fake_vif, vm_ref)
+
+
+class XenAPIBridgeDriverTestCase(XenVIFDriverTestBase, object):
+ def setUp(self):
+ super(XenAPIBridgeDriverTestCase, self).setUp()
+ self.bridge_driver = vif.XenAPIBridgeDriver(self._session)
+
+ @mock.patch.object(vif.XenAPIBridgeDriver, '_ensure_vlan_bridge',
+ return_value='fake_network_ref')
+ @mock.patch.object(vif.XenVIFDriver, '_create_vif',
+ return_value='fake_vif_ref')
+ def test_plug_create_vlan(self, mock_create_vif, mock_ensure_vlan_bridge):
+ instance = {'name': "fake_instance_name"}
+ network = model.Network()
+ network._set_meta({'should_create_vlan': True})
+ vif = model.VIF()
+ vif._set_meta({'rxtx_cap': 1})
+ vif['network'] = network
+ vif['address'] = "fake_address"
+ vm_ref = "fake_vm_ref"
+ device = 1
+ ret_vif_ref = self.bridge_driver.plug(instance, vif, vm_ref, device)
+ self.assertEqual('fake_vif_ref', ret_vif_ref)
+
+ @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ return_value='fake_vif_ref')
+ def test_unplug(self, mock_get_vif_ref):
+ instance = {'name': "fake_instance"}
+ vm_ref = "fake_vm_ref"
+ self.bridge_driver.unplug(instance, fake_vif, vm_ref)
+
+ expected = [mock.call('VIF.destroy', 'fake_vif_ref')]
+ self.assertEqual(expected, self._session.call_xenapi.call_args_list)
+
+
+class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
+ def setUp(self):
+ super(XenAPIOpenVswitchDriverTestCase, self).setUp()
+ self.ovs_driver = vif.XenAPIOpenVswitchDriver(self._session)
+
+ @mock.patch.object(network_utils, 'find_network_with_bridge',
+ return_value='fake_network_ref')
+ @mock.patch.object(vif.XenVIFDriver, '_create_vif',
+ return_value='fake_vif_ref')
+ @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref', return_value=None)
+ def test_plug(self, mock_get_vif_ref, mock_create_vif,
+ mock_find_network_with_bridge):
+ instance = {'name': "fake_instance_name"}
+ vm_ref = "fake_vm_ref"
+ device = 1
+ ret_vif_ref = self.ovs_driver.plug(instance, fake_vif, vm_ref, device)
+ self.assertEqual('fake_vif_ref', ret_vif_ref)
+
+ @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref',
+ return_value='fake_vif_ref')
+ def test_unplug(self, mock_get_vif_ref):
+ instance = {'name': "fake_instance"}
+ vm_ref = "fake_vm_ref"
+ self.ovs_driver.unplug(instance, fake_vif, vm_ref)
+
+ expected = [mock.call('VIF.destroy', 'fake_vif_ref')]
+ self.assertEqual(expected, self._session.call_xenapi.call_args_list)
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py
index 6b3218c..5c7a350 100644
--- a/nova/virt/xenapi/vif.py
+++ b/nova/virt/xenapi/vif.py
@@ -18,8 +18,11 @@
"""VIF drivers for XenAPI."""
from oslo_config import cfg
+from oslo_log import log as logging
+from nova import exception
from nova.i18n import _
+from nova.i18n import _LW
from nova.virt.xenapi import network_utils
from nova.virt.xenapi import vm_utils
@@ -31,11 +34,56 @@ xenapi_ovs_integration_bridge_opt = cfg.StrOpt('ovs_integration_bridge',
CONF = cfg.CONF
CONF.register_opt(xenapi_ovs_integration_bridge_opt, 'xenserver')
+LOG = logging.getLogger(__name__)
+
class XenVIFDriver(object):
def __init__(self, xenapi_session):
self._session = xenapi_session
+ def _get_vif_ref(self, vif, vm_ref):
+ vif_refs = self._session.call_xenapi("VM.get_VIFs", vm_ref)
+ for vif_ref in vif_refs:
+ try:
+ vif_rec = self._session.call_xenapi('VIF.get_record', vif_ref)
+ if vif_rec['MAC'] == vif['address']:
+ return vif_ref
+ except Exception:
+ # When got exception here, maybe the vif is removed during the
+ # loop, ignore this vif and continue
+ continue
+ return None
+
+ def _create_vif(self, vif, vif_rec, vm_ref):
+ try:
+ vif_ref = self._session.call_xenapi('VIF.create', vif_rec)
+ except Exception as e:
+ LOG.warn(_LW("Failed to create vif, exception:%(exception)s, "
+ "vif:%(vif)s"), {'exception': e, 'vif': vif})
+ raise exception.NovaException(
+ reason=_("Failed to create vif %s") % vif)
+
+ LOG.debug("create vif %(vif)s for vm %(vm_ref)s successfully",
+ {'vif': vif, 'vm_ref': vm_ref})
+ return vif_ref
+
+ def unplug(self, instance, vif, vm_ref):
+ try:
+ LOG.debug("unplug vif, vif:%(vif)s, vm_ref:%(vm_ref)s",
+ {'vif': vif, 'vm_ref': vm_ref}, instance=instance)
+ vif_ref = self._get_vif_ref(vif, vm_ref)
+ if not vif_ref:
+ LOG.debug("vif didn't exist, no need to unplug vif %s",
+ vif, instance=instance)
+ return
+ self._session.call_xenapi('VIF.destroy', vif_ref)
+ except Exception as e:
+ LOG.warn(
+ _LW("Fail to unplug vif:%(vif)s, exception:%(exception)s"),
+ {'vif': vif, 'exception': e}, instance=instance)
+ raise exception.NovaException(
+ reason=_("Failed to unplug vif %s") % vif)
+
class XenAPIBridgeDriver(XenVIFDriver):
"""VIF Driver for XenAPI that uses XenAPI to create Networks."""
@@ -43,6 +91,14 @@ class XenAPIBridgeDriver(XenVIFDriver):
def plug(self, instance, vif, vm_ref=None, device=None):
if not vm_ref:
vm_ref = vm_utils.lookup(self._session, instance['name'])
+
+ # if VIF already exists, return this vif_ref directly
+ vif_ref = self._get_vif_ref(vif, vm_ref)
+ if vif_ref:
+ LOG.debug("VIF %s already exists when plug vif",
+ vif_ref, instance=instance)
+ return vif_ref
+
if not device:
device = 0
@@ -65,7 +121,7 @@ class XenAPIBridgeDriver(XenVIFDriver):
else:
vif_rec['qos_algorithm_type'] = ''
vif_rec['qos_algorithm_params'] = {}
- return vif_rec
+ return self._create_vif(vif, vif_rec, vm_ref)
def _ensure_vlan_bridge(self, network):
"""Ensure that a VLAN bridge exists."""
@@ -126,8 +182,8 @@ class XenAPIBridgeDriver(XenVIFDriver):
return network_ref
- def unplug(self, instance, vif):
- pass
+ def unplug(self, instance, vif, vm_ref):
+ super(XenAPIBridgeDriver, self).unplug(instance, vif, vm_ref)
class XenAPIOpenVswitchDriver(XenVIFDriver):
@@ -137,6 +193,13 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
if not vm_ref:
vm_ref = vm_utils.lookup(self._session, instance['name'])
+ # if VIF already exists, return this vif_ref directly
+ vif_ref = self._get_vif_ref(vif, vm_ref)
+ if vif_ref:
+ LOG.debug("VIF %s already exists when plug vif",
+ vif_ref, instance=instance)
+ return vif_ref
+
if not device:
device = 0
@@ -155,7 +218,7 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
# OVS on the hypervisor monitors this key and uses it to
# set the iface-id attribute
vif_rec['other_config'] = {'nicira-iface-id': vif['id']}
- return vif_rec
+ return self._create_vif(vif, vif_rec, vm_ref)
- def unplug(self, instance, vif):
- pass
+ def unplug(self, instance, vif, vm_ref):
+ super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 294a2aa..65a41a3 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -1576,11 +1576,10 @@ class VMOps(object):
self._destroy_vdis(instance, vm_ref)
self._destroy_kernel_ramdisk(instance, vm_ref)
- vm_utils.destroy_vm(self._session, instance, vm_ref)
-
- self.unplug_vifs(instance, network_info)
+ self.unplug_vifs(instance, network_info, vm_ref)
self.firewall_driver.unfilter_instance(
instance, network_info=network_info)
+ vm_utils.destroy_vm(self._session, instance, vm_ref)
def pause(self, instance):
"""Pause VM instance."""
@@ -1896,25 +1895,18 @@ class VMOps(object):
self._session.call_xenapi("VM.get_domid", vm_ref)
for device, vif in enumerate(network_info):
- vif_rec = self.vif_driver.plug(instance, vif,
- vm_ref=vm_ref, device=device)
- network_ref = vif_rec['network']
- LOG.debug('Creating VIF for network %s',
- network_ref, instance=instance)
- vif_ref = self._session.call_xenapi('VIF.create', vif_rec)
- LOG.debug('Created VIF %(vif_ref)s, network %(network_ref)s',
- {'vif_ref': vif_ref, 'network_ref': network_ref},
- instance=instance)
+ LOG.debug('Create VIF %s', vif, instance=instance)
+ self.vif_driver.plug(instance, vif, vm_ref=vm_ref, device=device)
def plug_vifs(self, instance, network_info):
"""Set up VIF networking on the host."""
for device, vif in enumerate(network_info):
self.vif_driver.plug(instance, vif, device=device)
- def unplug_vifs(self, instance, network_info):
+ def unplug_vifs(self, instance, network_info, vm_ref):
if network_info:
for vif in network_info:
- self.vif_driver.unplug(instance, vif)
+ self.vif_driver.unplug(instance, vif, vm_ref)
def reset_network(self, instance, rescue=False):
"""Calls resetnetwork method in agent."""