Support MOS9 with un-merged patch in Mitaka

1. Create new patches based on Mitaka
2. Delete patches which are merged in Mitaka
3. Modify patching function
4. Modify building dom0 ISO script
5. Create new dom0 plugin ISO based on Mitaka

Change-Id: I8996da714f2b76a3b68e8bb6c0d58289467b5b1b
This commit is contained in:
Huan Xie 2016-07-19 21:51:35 -07:00 committed by John Hua
parent eac05bedc1
commit efae2fc22e
13 changed files with 1218 additions and 2049 deletions

View File

@ -20,7 +20,7 @@ LOG_ROOT = '/var/log/@PLUGIN_NAME@'
LOG_FILE = 'compute_post_deployment.log'
HIMN_IP = '169.254.0.1'
INT_BRIDGE = 'br-int'
XS_PLUGIN_ISO = 'xenapi-plugins-liberty.iso'
XS_PLUGIN_ISO = 'xenapi-plugins-mitaka.iso'
DIST_PACKAGES_DIR = '/usr/lib/python2.7/dist-packages/'
PLATFORM_VERSION = '1.9'
@ -450,23 +450,29 @@ def enable_linux_bridge(himn, username):
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
"""
Add patches which are not merged to upstream with order:
support-disable-image-cache.patch
speed-up-config-drive.patch
ovs-interim-bridge.patch
neutron-security-group.patch
"""
patchset_dir = sys.path[0]
patchfile_list = ['%s/patchset/vif-plug.patch' % patchset_dir,
'%s/patchset/nova-neutron-race-condition.patch' % patchset_dir,
patchfile_list = [
'%s/patchset/support-disable-image-cache.patch' % patchset_dir,
'%s/patchset/speed-up-config-drive.patch' % patchset_dir,
'%s/patchset/ovs-interim-bridge.patch' % patchset_dir,
'%s/patchset/neutron-security-group.patch' % patchset_dir,
'%s/patchset/speed-up-writing-config-drive.patch' % patchset_dir,
'%s/patchset/support-disable-image-cache.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)
"""
MO8's patch is not needed, keep the func here to add conntrack patch later
"""
pass
def apply_sm_patch(himn, username):

View File

@ -30,4 +30,6 @@ and then make them in one ISO.
*os-plugin-version: 2015.1*
*os-release: liberty*

View File

@ -64,7 +64,7 @@ rm -rf nova
git clone "$NOVA_GITREPO" nova
cd nova
git checkout -b mos_nova "$GITBRANCH"
# patch xenhost as this file is not merged to liberty
# patch xenhost as this file is not merged into this release
cp $DEPLOYMENT_SCRIPT_ROOT/patchset/xenhost plugins/xenserver/xenapi/etc/xapi.d/plugins/
cd ..
@ -81,10 +81,6 @@ rm -rf neutron
git clone "$NEUTRON_GITREPO" neutron
cd neutron
git checkout -b mos_neutron "$GITBRANCH"
# patch netwrap as this file is not merged to liberty
cp $DEPLOYMENT_SCRIPT_ROOT/patchset/netwrap \
neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/
chmod +x neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap
cd ..
cp -r xenserver-nova-suppack-builder/neutron/* \

View File

@ -1,82 +0,0 @@
#!/usr/bin/env python
# Copyright 2012 OpenStack Foundation
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
#
# XenAPI plugin for executing network commands (ovs, iptables, etc) on dom0
# Changes in this file merged post liberty, CommitID:
# b0cef88866db3d325974b1691ac3e1030144ee19
#
import gettext
gettext.install('neutron', unicode=1)
try:
import json
except ImportError:
import simplejson as json
import subprocess
import XenAPIPlugin
ALLOWED_CMDS = [
'ip',
'ipset',
'iptables-save',
'iptables-restore',
'ip6tables-save',
'ip6tables-restore',
'sysctl',
# NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
'ovs-ofctl',
'ovs-vsctl',
'ovsdb-client',
]
class PluginError(Exception):
"""Base Exception class for all plugin errors."""
def __init__(self, *args):
Exception.__init__(self, *args)
def _run_command(cmd, cmd_input):
"""Abstracts out the basics of issuing system commands. If the command
returns anything in stderr, a PluginError is raised with that information.
Otherwise, the output from stdout is returned.
"""
pipe = subprocess.PIPE
proc = subprocess.Popen(cmd, shell=False, stdin=pipe, stdout=pipe,
stderr=pipe, close_fds=True)
(out, err) = proc.communicate(cmd_input)
if proc.returncode != 0:
raise PluginError(err)
return out
def run_command(session, args):
cmd = json.loads(args.get('cmd'))
if cmd and cmd[0] not in ALLOWED_CMDS:
msg = _("Dom0 execution of '%s' is not permitted") % cmd[0]
raise PluginError(msg)
result = _run_command(cmd, json.loads(args.get('cmd_input', 'null')))
return json.dumps(result)
if __name__ == "__main__":
XenAPIPlugin.dispatch({"run_command": run_command})

View File

@ -1,24 +0,0 @@
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

@ -1,417 +1,410 @@
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 == []:
diff --git a/nova/tests/unit/virt/xenapi/test_vif.py b/nova/tests/unit/virt/xenapi/test_vif.py
index ebd3a9a..320a92a 100644
--- a/nova/tests/unit/virt/xenapi/test_vif.py
+++ b/nova/tests/unit/virt/xenapi/test_vif.py
@@ -22,6 +22,7 @@ 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,
@@ -190,6 +191,10 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
self.assertTrue(mock_create_vif.called)
self.assertEqual('fake_vif_ref', ret_vif_ref)
+ @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_bridge')
+ @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_port')
+ @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_device_exists',
+ return_value=True)
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_br')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_port')
@mock.patch.object(network_utils, 'find_network_with_name_label',
@@ -198,7 +203,10 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
def test_unplug(self, mock_super_unplug,
mock_find_network_with_name_label,
mock_ovs_del_port,
- mock_ovs_del_br):
+ mock_ovs_del_br,
+ mock_device_exists,
+ mock_delete_linux_port,
+ mock_delete_linux_bridge):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
@@ -241,10 +249,12 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
self.ovs_driver.unplug, instance, fake_vif,
vm_ref)
- @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_add_patch_port')
- @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_map_external_ids')
- def test_post_start_actions(self, mock_ovs_map_external_ids,
- mock_ovs_add_patch_port):
+ @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_brctl_add_if')
+ @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_create_linux_bridge')
+ @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_add_port')
+ def test_post_start_actions(self, mock_ovs_add_port,
+ mock_create_linux_bridge,
+ mock_brctl_add_if):
vif_ref = "fake_vif_ref"
instance = {'name': 'fake_instance_name'}
fake_vif_rec = {'uuid': fake_vif['uuid'],
@@ -267,8 +277,8 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
self.assertTrue(mock_VIF_get_record.called)
self.assertTrue(mock_network_get_bridge.called)
self.assertTrue(mock_network_get_uuid.called)
- self.assertEqual(mock_ovs_add_patch_port.call_count, 2)
- self.assertTrue(mock_ovs_map_external_ids.called)
+ self.assertEqual(mock_ovs_add_port.call_count, 1)
+ self.assertTrue(mock_brctl_add_if.called)
@mock.patch.object(network_utils, 'find_network_with_name_label',
return_value="exist_network_ref")
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py
index b9660be..a474d23 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
"""
super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref)
@@ -253,12 +253,10 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
LOG.debug('destroying patch port pair for vif: vif_id=%(vif_id)s',
{'vif_id': vif['id']})
bridge_name = self._session.network.get_bridge(network)
- patch_port1, patch_port2 = self._get_patch_port_pair_names(vif['id'])
+ patch_port1, tap_name = self._get_patch_port_pair_names(vif['id'])
try:
# delete the patch port pair
self._ovs_del_port(bridge_name, patch_port1)
- self._ovs_del_port(CONF.xenserver.ovs_integration_bridge,
- patch_port2)
except Exception as e:
LOG.warn(_LW("Failed to delete patch port pair for vif %(if)s,"
" exception:%(exception)s"),
@@ -277,6 +275,18 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
# won't be destroyed automatically by XAPI. So let's destroy it
# at here.
self._ovs_del_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._ovs_del_port(CONF.xenserver.ovs_integration_bridge, qvo_name)
except Exception as e:
LOG.warn(_LW("Failed to delete bridge for vif %(if)s, "
"exception:%(exception)s"),
@@ -284,6 +294,88 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
raise exception.VirtualInterfaceUnplugException(
reason=_("Failed to delete bridge"))
+ 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:
+ cmd = 'ip_link_get_dev'
+ args = {'device_name': device}
+ self._exec_dom0_cmd(cmd, args)
+ 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)
+ args = {'device_name': dev}
+ self._exec_dom0_cmd('ip_link_del_dev', 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.
+ """
+ LOG.debug("Create veth pair, port1:%(qvb)s, port2:%(qvo)s",
+ {'qvb': dev1_name, 'qvo': dev2_name})
+ for dev in [dev1_name, dev2_name]:
+ self._delete_net_dev(dev)
+ args = {'dev1_name': dev1_name, 'dev2_name': dev2_name}
+ self._exec_dom0_cmd('ip_link_add_veth_pair', args)
+ for dev in [dev1_name, dev2_name]:
+ args = {'device_name': dev, 'option': 'up'}
+ self._exec_dom0_cmd('ip_link_set_dev', args)
+ args = {'device_name': dev, 'option': 'on'}
+ self._exec_dom0_cmd('ip_link_set_promisc', 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)
+ self._brctl_add_br(linux_br_name)
+ self._brctl_set_fd(linux_br_name, '0')
+ self._brctl_set_stp(linux_br_name, 'off')
+ args = {'device_name': linux_br_name, 'option': 'up'}
+ self._exec_dom0_cmd('ip_link_set_dev', 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)
+ self._brctl_add_if(linux_br_name, qvb_name)
+ self._ovs_add_port(CONF.xenserver.ovs_integration_bridge, qvo_name)
+ self._ovs_map_external_ids(qvo_name, vif_rec)
+ return linux_br_name
+
+ def _delete_linux_port(self, qbr_name, port_name):
+ try:
+ # delete port in linux bridge
+ self._brctl_del_if(qbr_name, port_name)
+ 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
+ args = {'device_name': qbr_name, 'option': 'down'}
+ self._exec_dom0_cmd('ip_link_set_dev', args)
+ self._brctl_del_br(qbr_name)
+ 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;
@@ -295,19 +387,25 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
bridge_name = self._session.network.get_bridge(network_ref)
network_uuid = self._session.network.get_uuid(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_uuid=%(uuid)s, bridge_name=%(bridge_name)s',
- {'port1': patch_port1, 'port2': patch_port2,
+ {'port1': patch_port1, 'port2': tap_name,
'uuid': network_uuid, 'bridge_name': bridge_name})
if bridge_name is None:
raise exception.VirtualInterfacePlugException(
_("Failed to find bridge for vif"))
- self._ovs_add_patch_port(bridge_name, patch_port1, patch_port2)
- self._ovs_add_patch_port(CONF.xenserver.ovs_integration_bridge,
- patch_port2, patch_port1)
- self._ovs_map_external_ids(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 %(interim_bridge)s and "
+ "linux bridge %(linux_bridge)s",
+ {'interim_bridge': bridge_name,
+ 'linux_bridge': linux_br_name})
+ self._create_veth_pair(tap_name, patch_port1)
+ self._brctl_add_if(linux_br_name, tap_name)
+ # Add port to interim bridge
+ self._ovs_add_port(bridge_name, patch_port1)
def get_vif_interim_net_name(self, vif):
return ("net-" + vif['id'])[:network_model.NIC_NAME_LEN]
@@ -335,14 +433,13 @@ 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 _ovs_add_patch_port(self, bridge_name, port_name, peer_port_name):
- cmd = 'ovs_add_patch_port'
+ def _ovs_add_port(self, bridge_name, port_name):
+ cmd = 'ovs_add_port'
args = {'bridge_name': bridge_name,
- 'port_name': port_name,
- 'peer_port_name': peer_port_name
+ 'port_name': port_name
}
self._exec_dom0_cmd(cmd, args)
@@ -378,6 +475,40 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
self._ovs_set_if_external_id(interface, 'xs-vif-uuid', vif_uuid)
self._ovs_set_if_external_id(interface, 'iface-status', status)
+ def _brctl_add_if(self, bridge_name, interface_name):
+ cmd = 'brctl_add_if'
+ args = {'bridge_name': bridge_name,
+ 'interface_name': interface_name}
+ self._exec_dom0_cmd(cmd, args)
+
+ def _brctl_del_if(self, bridge_name, interface_name):
+ cmd = 'brctl_del_if'
+ args = {'bridge_name': bridge_name,
+ 'interface_name': interface_name}
+ self._exec_dom0_cmd(cmd, args)
+
+ def _brctl_del_br(self, bridge_name):
+ cmd = 'brctl_del_br'
+ args = {'bridge_name': bridge_name}
+ self._exec_dom0_cmd(cmd, args)
+
+ def _brctl_add_br(self, bridge_name):
+ cmd = 'brctl_add_br'
+ args = {'bridge_name': bridge_name}
+ self._exec_dom0_cmd(cmd, args)
+
+ def _brctl_set_fd(self, bridge_name, fd):
+ cmd = 'brctl_set_fd'
+ args = {'bridge_name': bridge_name,
+ 'fd': fd}
+ self._exec_dom0_cmd(cmd, args)
+
+ def _brctl_set_stp(self, bridge_name, stp_opt):
+ cmd = 'brctl_set_stp'
+ args = {'bridge_name': bridge_name,
+ 'option': stp_opt}
+ self._exec_dom0_cmd(cmd, args)
+
def _exec_dom0_cmd(self, cmd, cmd_args):
args = {'cmd': cmd,
'args': cmd_args
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
index ed0ff0f..045f362 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
@@ -252,12 +252,105 @@ def _ovs_set_if_external_id(args):
return _run_command(cmd_args)
+def _ovs_add_port(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ port_name = pluginlib.exists(args, 'port_name')
+ cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port_name,
+ '--', 'add-port', bridge_name, port_name]
+ return _run_command(cmd_args)
+
+
+def _ip_link_get_dev(args):
+ device_name = pluginlib.exists(args, 'device_name')
+ cmd_args = ['ip', 'link', 'show', device_name]
+ return _run_command(cmd_args)
+
+
+def _ip_link_del_dev(args):
+ device_name = pluginlib.exists(args, 'device_name')
+ cmd_args = ['ip', 'link', 'delete', device_name]
+ return _run_command(cmd_args)
+
+def _ip_link_add_veth_pair(args):
+ dev1_name = pluginlib.exists(args, 'dev1_name')
+ dev2_name = pluginlib.exists(args, 'dev2_name')
+ cmd_args = ['ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
+ 'name', dev2_name]
+ return _run_command(cmd_args)
+
+
+def _ip_link_set_dev(args):
+ device_name = pluginlib.exists(args, 'device_name')
+ option = pluginlib.exists(args, 'option')
+ cmd_args = ['ip', 'link', 'set', device_name, option]
+ return _run_command(cmd_args)
+
+
+def _ip_link_set_promisc(args):
+ device_name = pluginlib.exists(args, 'device_name')
+ option = pluginlib.exists(args, 'option')
+ cmd_args = ['ip', 'link', 'set', device_name, 'promisc', option]
+ return _run_command(cmd_args)
+
+
+def _brctl_add_br(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ cmd_args = ['brctl', 'addbr', bridge_name]
+ return _run_command(cmd_args)
+
+
+def _brctl_del_br(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ cmd_args = ['brctl', 'delbr', bridge_name]
+ return _run_command(cmd_args)
+
+
+def _brctl_set_fd(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ fd = pluginlib.exists(args, 'fd')
+ cmd_args = ['brctl', 'setfd', bridge_name, fd]
+ return _run_command(cmd_args)
+
+
+def _brctl_set_stp(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ option = pluginlib.exists(args, 'option')
+ cmd_args = ['brctl', 'stp', bridge_name, option]
+ return _run_command(cmd_args)
+
+
+def _brctl_add_if(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ if_name = pluginlib.exists(args, 'interface_name')
+ cmd_args = ['brctl', 'addif', bridge_name, if_name]
+ return _run_command(cmd_args)
+
+
+def _brctl_del_if(args):
+ bridge_name = pluginlib.exists(args, 'bridge_name')
+ if_name = pluginlib.exists(args, 'interface_name')
+ cmd_args = ['brctl', 'delif', bridge_name, if_name]
+ return _run_command(cmd_args)
+
+
ALLOWED_NETWORK_CMDS = {
# allowed cmds to config OVS bridge
'ovs_add_patch_port': _ovs_add_patch_port,
+ 'ovs_add_port': _ovs_add_port,
'ovs_del_port': _ovs_del_port,
'ovs_del_br': _ovs_del_br,
- 'ovs_set_if_external_id': _ovs_set_if_external_id
+ 'ovs_set_if_external_id': _ovs_set_if_external_id,
+ 'ip_link_add_veth_pair': _ip_link_add_veth_pair,
+ 'ip_link_del_dev': _ip_link_del_dev,
+ 'ip_link_get_dev': _ip_link_get_dev,
+ 'ip_link_set_dev': _ip_link_set_dev,
+ 'ip_link_set_promisc': _ip_link_set_promisc,
+ 'brctl_add_br': _brctl_add_br,
+ 'brctl_add_if': _brctl_add_if,
+ 'brctl_del_br': _brctl_del_br,
+ 'brctl_del_if': _brctl_del_if,
+ 'brctl_set_fd': _brctl_set_fd,
+ 'brctl_set_stp': _brctl_set_stp
}

View File

@ -1,368 +0,0 @@
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

@ -1,8 +1,8 @@
diff --git a/nova/tests/unit/virt/xenapi/test_vm_utils.py b/nova/tests/unit/virt/xenapi/test_vm_utils.py
index e15cc53..a777fcc 100644
index 174b5cf..d65385e 100644
--- a/nova/tests/unit/virt/xenapi/test_vm_utils.py
+++ b/nova/tests/unit/virt/xenapi/test_vm_utils.py
@@ -189,7 +189,7 @@ class GenerateConfigDriveTestCase(VMUtilsTestBase):
@@ -190,7 +190,7 @@ class GenerateConfigDriveTestCase(VMUtilsTestBase):
'-publisher', mox.IgnoreArg(), '-quiet',
'-J', '-r', '-V', 'config-2', mox.IgnoreArg(),
attempts=1, run_as_root=False).AndReturn(None)
@ -12,10 +12,10 @@ index e15cc53..a777fcc 100644
self.mox.StubOutWithMock(vm_utils, 'create_vbd')
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index c8fdc52..9bbf173 100644
index f88cb3a..982ad37 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -138,6 +138,7 @@ MBR_SIZE_BYTES = MBR_SIZE_SECTORS * SECTOR_SIZE
@@ -137,6 +137,7 @@ MBR_SIZE_BYTES = MBR_SIZE_SECTORS * SECTOR_SIZE
KERNEL_DIR = '/boot/guest'
MAX_VDI_CHAIN_SIZE = 16
PROGRESS_INTERVAL_SECONDS = 300
@ -23,7 +23,7 @@ index c8fdc52..9bbf173 100644
# Fudge factor to allow for the VHD chain to be slightly larger than
# the partitioned space. Otherwise, legitimate images near their
@@ -1162,6 +1163,7 @@ def generate_configdrive(session, instance, vm_ref, userdevice,
@@ -1159,6 +1160,7 @@ def generate_configdrive(session, instance, vm_ref, userdevice,
utils.execute('dd',
'if=%s' % tmp_file,
'of=%s' % dev_path,
@ -31,7 +31,7 @@ index c8fdc52..9bbf173 100644
'oflag=direct,sync',
run_as_root=True)
@@ -2429,6 +2431,7 @@ def _copy_partition(session, src_ref, dst_ref, partition, virtual_size):
@@ -2426,6 +2428,7 @@ def _copy_partition(session, src_ref, dst_ref, partition, virtual_size):
utils.execute('dd',
'if=%s' % src_path,
'of=%s' % dst_path,

View File

@ -1,8 +1,8 @@
diff --git a/nova/tests/unit/virt/xenapi/test_vm_utils.py b/nova/tests/unit/virt/xenapi/test_vm_utils.py
index e15cc53..77a1968 100644
index 1305d05..174b5cf 100644
--- a/nova/tests/unit/virt/xenapi/test_vm_utils.py
+++ b/nova/tests/unit/virt/xenapi/test_vm_utils.py
@@ -1437,6 +1437,46 @@ class CreateKernelRamdiskTestCase(VMUtilsTestBase):
@@ -1455,6 +1455,46 @@ class CreateKernelRamdiskTestCase(VMUtilsTestBase):
self.session, self.instance, self.name_label)
self.assertEqual(("k", None), result)
@ -50,10 +50,10 @@ index e15cc53..77a1968 100644
class ScanSrTestCase(VMUtilsTestBase):
@mock.patch.object(vm_utils, "_scan_sr")
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index c8fdc52..5312467 100644
index 583a913..f88cb3a 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1182,7 +1182,7 @@ def _create_kernel_image(context, session, instance, name_label, image_id,
@@ -1179,7 +1179,7 @@ def _create_kernel_image(context, session, instance, name_label, image_id,
Returns: A list of dictionaries that describe VDIs
"""
filename = ""
@ -62,7 +62,7 @@ index c8fdc52..5312467 100644
args = {}
args['cached-image'] = image_id
args['new-image-uuid'] = str(uuid.uuid4())
@@ -1572,7 +1572,7 @@ def _fetch_disk_image(context, session, instance, name_label, image_id,
@@ -1569,7 +1569,7 @@ def _fetch_disk_image(context, session, instance, name_label, image_id,
# Let the plugin copy the correct number of bytes.
args['image-size'] = str(vdi_size)

View File

@ -1,376 +0,0 @@
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."""

View File

@ -20,11 +20,10 @@
# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace
# which means the Nova xenapi plugins must use only Python 2.4 features
# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true
#
# XenAPI plugin for host operations
# Changes in this file will be merged post liberty
# OVS interim bridge: https://review.openstack.org/#/c/242846/29
# Neutron security group: https://review.openstack.org/#/c/251271/10
#
try:
@ -73,24 +72,27 @@ def _run_command(cmd, cmd_input=None):
"""
try:
return utils.run_command(cmd, cmd_input=cmd_input)
except utils.SubprocessException, e:
except utils.SubprocessException, e: # noqa
raise pluginlib.PluginError(e.err)
def _resume_compute(session, compute_ref, compute_uuid):
"""Resume compute node on slave host after pool join. This has to
happen regardless of the success or failure of the join operation."""
"""Resume compute node on slave host after pool join.
This has to happen regardless of the success or failure of the join
operation.
"""
try:
# session is valid if the join operation has failed
session.xenapi.VM.start(compute_ref, False, True)
except XenAPI.Failure, e:
except XenAPI.Failure:
# if session is invalid, e.g. xapi has restarted, then the pool
# join has been successful, wait for xapi to become alive again
for c in xrange(0, DEFAULT_TRIES):
for c in range(0, DEFAULT_TRIES):
try:
_run_command(["xe", "vm-start", "uuid=%s" % compute_uuid])
return
except pluginlib.PluginError, e:
except pluginlib.PluginError:
logging.exception('Waited %d seconds for the slave to '
'become available.' % (c * DEFAULT_SLEEP))
time.sleep(DEFAULT_SLEEP)
@ -159,7 +161,7 @@ def get_config(self, arg_dict):
params = arg_dict["params"]
try:
dct = json.loads(params)
except Exception, e:
except Exception:
dct = params
key = dct["key"]
ret = conf.get(key)
@ -176,7 +178,7 @@ def set_config(self, arg_dict):
params = arg_dict["params"]
try:
dct = json.loads(params)
except Exception, e:
except Exception:
dct = params
key = dct["key"]
val = dct["value"]
@ -207,31 +209,164 @@ def iptables_config(session, args):
'ip6tables-save',
'ip6tables-restore'):
result = _run_command(cmd, process_input)
ret_str = json.dumps(dict(out=result,
err=''))
ret_str = json.dumps(dict(out=result, err=''))
logging.debug("iptables_config:exit")
return ret_str
else:
# else don't do anything and return an error
else:
raise pluginlib.PluginError(_("Invalid iptables command"))
def _ovs_add_patch_port(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
port_name = pluginlib.exists(args, 'port_name')
peer_port_name = pluginlib.exists(args, '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]
return _run_command(cmd_args)
def _ovs_del_port(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
port_name = pluginlib.exists(args, 'port_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port',
bridge_name, port_name]
return _run_command(cmd_args)
def _ovs_del_br(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists',
'del-br', bridge_name]
return _run_command(cmd_args)
def _ovs_set_if_external_id(args):
interface = pluginlib.exists(args, 'interface')
extneral_id = pluginlib.exists(args, 'extneral_id')
value = pluginlib.exists(args, 'value')
cmd_args = ['ovs-vsctl', 'set', 'Interface', interface,
'external-ids:%s=%s' % (extneral_id, value)]
return _run_command(cmd_args)
def _ovs_add_port(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
port_name = pluginlib.exists(args, 'port_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port_name,
'--', 'add-port', bridge_name, port_name]
return _run_command(cmd_args)
def _ip_link_get_dev(args):
device_name = pluginlib.exists(args, 'device_name')
cmd_args = ['ip', 'link', 'show', device_name]
return _run_command(cmd_args)
def _ip_link_del_dev(args):
device_name = pluginlib.exists(args, 'device_name')
cmd_args = ['ip', 'link', 'delete', device_name]
return _run_command(cmd_args)
def _ip_link_add_veth_pair(args):
dev1_name = pluginlib.exists(args, 'dev1_name')
dev2_name = pluginlib.exists(args, 'dev2_name')
cmd_args = ['ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
'name', dev2_name]
return _run_command(cmd_args)
def _ip_link_set_dev(args):
device_name = pluginlib.exists(args, 'device_name')
option = pluginlib.exists(args, 'option')
cmd_args = ['ip', 'link', 'set', device_name, option]
return _run_command(cmd_args)
def _ip_link_set_promisc(args):
device_name = pluginlib.exists(args, 'device_name')
option = pluginlib.exists(args, 'option')
cmd_args = ['ip', 'link', 'set', device_name, 'promisc', option]
return _run_command(cmd_args)
def _brctl_add_br(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
cmd_args = ['brctl', 'addbr', bridge_name]
return _run_command(cmd_args)
def _brctl_del_br(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
cmd_args = ['brctl', 'delbr', bridge_name]
return _run_command(cmd_args)
def _brctl_set_fd(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
fd = pluginlib.exists(args, 'fd')
cmd_args = ['brctl', 'setfd', bridge_name, fd]
return _run_command(cmd_args)
def _brctl_set_stp(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
option = pluginlib.exists(args, 'option')
cmd_args = ['brctl', 'stp', bridge_name, option]
return _run_command(cmd_args)
def _brctl_add_if(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
if_name = pluginlib.exists(args, 'interface_name')
cmd_args = ['brctl', 'addif', bridge_name, if_name]
return _run_command(cmd_args)
def _brctl_del_if(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
if_name = pluginlib.exists(args, 'interface_name')
cmd_args = ['brctl', 'delif', bridge_name, if_name]
return _run_command(cmd_args)
ALLOWED_NETWORK_CMDS = {
# allowed cmds to config OVS bridge
'ovs_add_patch_port': _ovs_add_patch_port,
'ovs_add_port': _ovs_add_port,
'ovs_del_port': _ovs_del_port,
'ovs_del_br': _ovs_del_br,
'ovs_set_if_external_id': _ovs_set_if_external_id,
'ip_link_add_veth_pair': _ip_link_add_veth_pair,
'ip_link_del_dev': _ip_link_del_dev,
'ip_link_get_dev': _ip_link_get_dev,
'ip_link_set_dev': _ip_link_set_dev,
'ip_link_set_promisc': _ip_link_set_promisc,
'brctl_add_br': _brctl_add_br,
'brctl_add_if': _brctl_add_if,
'brctl_del_br': _brctl_del_br,
'brctl_del_if': _brctl_del_if,
'brctl_set_fd': _brctl_set_fd,
'brctl_set_stp': _brctl_set_stp
}
def network_config(session, args):
# 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 == []:
msg = _("empty command is supplied")
"""network config functions"""
cmd = pluginlib.exists(args, 'cmd')
if not isinstance(cmd, basestring):
msg = _("invalid command '%s'") % str(cmd)
raise pluginlib.PluginError(msg)
if cmd[0] not in ALLOWED_CMDS:
msg = _("Dom0 execution of '%s' is not permitted") % cmd[0]
return
if cmd not in ALLOWED_NETWORK_CMDS:
msg = _("Dom0 execution of '%s' is not permitted") % cmd
raise pluginlib.PluginError(msg)
result = _run_command(cmd, json.loads(args.get('cmd_input', 'null')))
return json.dumps(result)
return
cmd_args = pluginlib.exists(args, 'args')
return ALLOWED_NETWORK_CMDS[cmd](cmd_args)
def _power_action(action, arg_dict):
@ -245,9 +380,9 @@ def _power_action(action, arg_dict):
"resident-on=%s" % host_uuid])
if result:
raise pluginlib.PluginError(result)
cmds = {"reboot": "host-reboot",
"startup": "host-power-on",
"shutdown": "host-shutdown",}
cmds = {"reboot": "host-reboot",
"startup": "host-power-on",
"shutdown": "host-shutdown"}
result = _run_command(["xe", cmds[action], "uuid=%s" % host_uuid])
# Should be empty string
if result:
@ -269,22 +404,27 @@ def host_shutdown(self, arg_dict):
@jsonify
def host_start(self, arg_dict):
"""Starts the host. Currently not feasible, since the host
runs on the same machine as Xen.
"""Starts the host.
Currently not feasible, since the host runs on the same machine as
Xen.
"""
return _power_action("startup", arg_dict)
@jsonify
def host_join(self, arg_dict):
"""Join a remote host into a pool whose master is the host
where the plugin is called from. The following constraints apply:
"""Join a remote host into a pool.
- The host must have no VMs running, except nova-compute, which will be
shut down (and restarted upon pool-join) automatically,
The pool's master is the host where the plugin is called from. The
following constraints apply:
- The host must have no VMs running, except nova-compute, which
will be shut down (and restarted upon pool-join) automatically,
- The host must have no shared storage currently set up,
- The host must have the same license of the master,
- The host must have the same supplemental packs as the master."""
- The host must have the same supplemental packs as the master.
"""
session = XenAPI.Session(arg_dict.get("url"))
session.login_with_password(arg_dict.get("user"),
arg_dict.get("password"))
@ -425,12 +565,14 @@ def cleanup(dct):
# "external-auth-service-name", "")
return out
def query_gc(session, sr_uuid, vdi_uuid):
result = _run_command(["/opt/xensource/sm/cleanup.py",
"-q", "-u", sr_uuid])
# Example output: "Currently running: True"
return result[19:].strip() == "True"
def get_pci_device_details(session):
"""Returns a string that is a list of pci devices with details.
@ -463,10 +605,12 @@ def get_pci_type(session, pci_device):
if __name__ == "__main__":
# Support both serialized and non-serialized plugin approaches
_, methodname = xmlrpclib.loads(sys.argv[1])
if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type']:
if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type',
'network_config']:
utils.register_plugin_calls(query_gc,
get_pci_device_details,
get_pci_type)
get_pci_type,
network_config)
XenAPIPlugin.dispatch(
{"host_data": host_data,
@ -478,5 +622,4 @@ if __name__ == "__main__":
"get_config": get_config,
"set_config": set_config,
"iptables_config": iptables_config,
"network_config": network_config,
"host_uptime": host_uptime})