diff options
33 files changed, 427 insertions, 107 deletions
diff --git a/nova/api/openstack/compute/personality.py b/nova/api/openstack/compute/personality.py index 2d0aecfe21..4d15fb851d 100644 --- a/nova/api/openstack/compute/personality.py +++ b/nova/api/openstack/compute/personality.py @@ -49,9 +49,8 @@ class Personality(extensions.V21APIExtensionBase): # server_update & server_rebuild def server_create(self, server_dict, create_kwargs, body_deprecated_param=None): - if 'personality' in server_dict: - create_kwargs['injected_files'] = self._get_injected_files( - server_dict['personality']) + create_kwargs['injected_files'] = self._get_injected_files( + server_dict.get('personality', [])) def server_rebuild(self, server_dict, create_kwargs, body_deprecated_param=None): diff --git a/nova/cmd/novnc.py b/nova/cmd/novnc.py index dc96e2fac1..0b69e5f3b6 100644 --- a/nova/cmd/novnc.py +++ b/nova/cmd/novnc.py @@ -16,9 +16,11 @@ from oslo_config import cfg opts = [ - cfg.BoolOpt('record', - default=False, - help='Record sessions to FILE.[session_number]'), + cfg.StrOpt('record', + help='This is the filename that will be used for storing ' + 'websocket frames received and sent by a proxy service ' + '(like VNC, spice, serial) running on this host. If this ' + 'is not set (default), no recording will be done.'), cfg.BoolOpt('daemon', default=False, help='Become a daemon (background process)'), diff --git a/nova/compute/api.py b/nova/compute/api.py index 32ca41fd73..f16f6c4cad 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -937,7 +937,13 @@ class API(base.Base): block_device.properties_root_device_name( boot_meta.get('properties', {}))) - image_meta = objects.ImageMeta.from_dict(boot_meta) + try: + image_meta = objects.ImageMeta.from_dict(boot_meta) + except ValueError as e: + # there must be invalid values in the image meta properties so + # consider this an invalid request + msg = _('Invalid image metadata. Error: %s') % six.text_type(e) + raise exception.InvalidRequest(msg) numa_topology = hardware.numa_get_constraints( instance_type, image_meta) @@ -2526,7 +2532,7 @@ class API(base.Base): elevated, instance.uuid, 'finished') # reverse quota reservation for increased resource usage - deltas = compute_utils.reverse_upsize_quota_delta(context, migration) + deltas = compute_utils.reverse_upsize_quota_delta(context, instance) quotas = compute_utils.reserve_quota_delta(context, deltas, instance) instance.task_state = task_states.RESIZE_REVERTING diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 88a878e4bb..69841d2431 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2296,10 +2296,19 @@ class ComputeManager(manager.Manager): instance=instance) except (cinder_exception.EndpointNotFound, keystone_exception.EndpointNotFound) as exc: - LOG.warning(_LW('Ignoring EndpointNotFound: %s'), exc, + LOG.warning(_LW('Ignoring EndpointNotFound for ' + 'volume %(volume_id)s: %(exc)s'), + {'exc': exc, 'volume_id': bdm.volume_id}, instance=instance) except cinder_exception.ClientException as exc: - LOG.warning(_LW('Ignoring Unknown cinder exception: %s'), exc, + LOG.warning(_LW('Ignoring unknown cinder exception for ' + 'volume %(volume_id)s: %(exc)s'), + {'exc': exc, 'volume_id': bdm.volume_id}, + instance=instance) + except Exception as exc: + LOG.warning(_LW('Ignoring unknown exception for ' + 'volume %(volume_id)s: %(exc)s'), + {'exc': exc, 'volume_id': bdm.volume_id}, instance=instance) if notify: diff --git a/nova/compute/utils.py b/nova/compute/utils.py index df820e5f7a..3f8a8cafa0 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -455,16 +455,12 @@ def upsize_quota_delta(context, new_flavor, old_flavor): return resize_quota_delta(context, new_flavor, old_flavor, 1, 1) -def reverse_upsize_quota_delta(context, migration_ref): +def reverse_upsize_quota_delta(context, instance): """Calculate deltas required to reverse a prior upsizing quota adjustment. """ - old_flavor = objects.Flavor.get_by_id( - context, migration_ref['old_instance_type_id']) - new_flavor = objects.Flavor.get_by_id( - context, migration_ref['new_instance_type_id']) - - return resize_quota_delta(context, new_flavor, old_flavor, -1, -1) + return resize_quota_delta(context, instance.new_flavor, + instance.old_flavor, -1, -1) def downsize_quota_delta(context, instance): diff --git a/nova/exception.py b/nova/exception.py index a8cc90f099..5e9faefb99 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1121,7 +1121,7 @@ class FlavorAccessNotFound(NotFound): class FlavorExtraSpecUpdateCreateFailed(NovaException): - msg_fmt = _("Flavor %(id)d extra spec cannot be updated or created " + msg_fmt = _("Flavor %(id)s extra spec cannot be updated or created " "after %(retries)d retries.") diff --git a/nova/image/glance.py b/nova/image/glance.py index b00c5da169..6344a3cb11 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -18,6 +18,7 @@ from __future__ import absolute_import import copy +import inspect import itertools import random import sys @@ -227,7 +228,12 @@ class GlanceClientWrapper(object): client = self.client or self._create_onetime_client(context, version) try: - return getattr(client.images, method)(*args, **kwargs) + result = getattr(client.images, method)(*args, **kwargs) + if inspect.isgenerator(result): + # Convert generator results to a list, so that we can + # catch any potential exceptions now and retry the call. + return list(result) + return result except retry_excs as e: host = self.host port = self.port diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 6487580a28..a0eb22d192 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -37,6 +37,7 @@ import six from nova import exception from nova.i18n import _, _LE, _LW +from nova.network import model as network_model from nova import objects from nova import paths from nova.pci import utils as pci_utils @@ -1342,7 +1343,7 @@ def _set_device_mtu(dev, mtu=None): check_exit_code=[0, 2, 254]) -def _create_veth_pair(dev1_name, dev2_name): +def _create_veth_pair(dev1_name, dev2_name, mtu=None): """Create a pair of veth devices with the specified names, deleting any previous devices with those names. """ @@ -1355,7 +1356,7 @@ def _create_veth_pair(dev1_name, dev2_name): utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True) utils.execute('ip', 'link', 'set', dev, 'promisc', 'on', run_as_root=True) - _set_device_mtu(dev) + _set_device_mtu(dev, mtu) def _ovs_vsctl(args): @@ -1368,15 +1369,34 @@ def _ovs_vsctl(args): raise exception.AgentError(method=full_args) -def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id): - _ovs_vsctl(['--', '--if-exists', 'del-port', dev, '--', - 'add-port', bridge, dev, - '--', 'set', 'Interface', dev, - 'external-ids:iface-id=%s' % iface_id, - 'external-ids:iface-status=active', - 'external-ids:attached-mac=%s' % mac, - 'external-ids:vm-uuid=%s' % instance_id]) - _set_device_mtu(dev) +def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, + instance_id, interface_type=None): + cmd = ['--', '--if-exists', 'del-port', dev, '--', + 'add-port', bridge, dev, + '--', 'set', 'Interface', dev, + 'external-ids:iface-id=%s' % iface_id, + 'external-ids:iface-status=active', + 'external-ids:attached-mac=%s' % mac, + 'external-ids:vm-uuid=%s' % instance_id] + if interface_type: + cmd += ['type=%s' % interface_type] + return cmd + + +def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, + mtu=None, interface_type=None): + _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, + mac, instance_id, + interface_type)) + # Note at present there is no support for setting the + # mtu for vhost-user type ports. + if interface_type != network_model.OVS_VHOSTUSER_INTERFACE_TYPE: + _set_device_mtu(dev, mtu) + else: + LOG.debug("MTU not set on %(interface_name)s interface " + "of type %(interface_type)s.", + {'interface_name': dev, + 'interface_type': interface_type}) def delete_ovs_vif_port(bridge, dev): @@ -1384,10 +1404,6 @@ def delete_ovs_vif_port(bridge, dev): delete_net_dev(dev) -def ovs_set_vhostuser_port_type(dev): - _ovs_vsctl(['--', 'set', 'Interface', dev, 'type=dpdkvhostuser']) - - def create_ivs_vif_port(dev, iface_id, mac, instance_id): utils.execute('ivs-ctl', 'add-port', dev, run_as_root=True) diff --git a/nova/network/model.py b/nova/network/model.py index c7376f862d..c7979d7c2a 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -77,6 +77,8 @@ VIF_DETAILS_VHOSTUSER_SOCKET = 'vhostuser_socket' # Specifies whether vhost-user socket should be plugged # into ovs bridge. Valid values are True and False VIF_DETAILS_VHOSTUSER_OVS_PLUG = 'vhostuser_ovs_plug' +# ovs vhost user interface type name +OVS_VHOSTUSER_INTERFACE_TYPE = 'dpdkvhostuser' # Constants for dictionary keys in the 'vif_details' field that are # valid for VIF_TYPE_TAP. diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index c04b60f722..7fb31c947b 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1602,10 +1602,12 @@ class API(base_api.NetworkAPI): def _nw_info_build_network(self, port, networks, subnets): network_name = None + network_mtu = None for net in networks: if port['network_id'] == net['id']: network_name = net['name'] tenant_id = net['tenant_id'] + network_mtu = net.get('mtu') break else: tenant_id = port['tenant_id'] @@ -1648,7 +1650,8 @@ class API(base_api.NetworkAPI): bridge=bridge, injected=CONF.flat_injected, label=network_name, - tenant_id=tenant_id + tenant_id=tenant_id, + mtu=network_mtu ) network['subnets'] = subnets port_profile = port.get('binding:profile') diff --git a/nova/objects/monitor_metric.py b/nova/objects/monitor_metric.py index 7d6c350a41..0ad599b366 100644 --- a/nova/objects/monitor_metric.py +++ b/nova/objects/monitor_metric.py @@ -97,8 +97,18 @@ class MonitorMetricList(base.ObjectListBase, base.NovaObject): :returns: a MonitorMetricList Object. """ metrics = jsonutils.loads(metrics) if metrics else [] - metric_list = [ - MonitorMetric(**metric) for metric in metrics] + + # NOTE(suro-patz): While instantiating the MonitorMetric() from + # JSON-ified string, we need to re-convert the + # normalized metrics to avoid truncation to 0 by + # typecasting into an integer. + metric_list = [] + for metric in metrics: + if ('value' in metric and metric['name'] in + FIELDS_REQUIRING_CONVERSION): + metric['value'] = metric['value'] * 100 + metric_list.append(MonitorMetric(**metric)) + return MonitorMetricList(objects=metric_list) # NOTE(jaypipes): This method exists to convert the object to the diff --git a/nova/tests/functional/regressions/test_bug_1558866.py b/nova/tests/functional/regressions/test_bug_1558866.py new file mode 100644 index 0000000000..378b629aa8 --- /dev/null +++ b/nova/tests/functional/regressions/test_bug_1558866.py @@ -0,0 +1,77 @@ +# Copyright 2016 IBM Corp. +# +# 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 datetime + +from oslo_config import cfg + +from nova import test +from nova.tests import fixtures as nova_fixtures +from nova.tests.functional.api import client as api_client +from nova.tests.unit.image import fake as fake_image +from nova.tests.unit import policy_fixture + +CONF = cfg.CONF +CONF.import_opt('null_kernel', 'nova.compute.api') + + +class TestServerGet(test.TestCase): + + def setUp(self): + super(TestServerGet, self).setUp() + self.useFixture(policy_fixture.RealPolicyFixture()) + api_fixture = self.useFixture(nova_fixtures.OSAPIFixture( + api_version='v2.1')) + + self.api = api_fixture.api + + # the image fake backend needed for image discovery + image_service = fake_image.stub_out_image_service(self) + self.addCleanup(fake_image.FakeImageService_reset) + + # NOTE(mriedem): This image has an invalid architecture metadata value + # and is used for negative testing in the functional stack. + timestamp = datetime.datetime(2011, 1, 1, 1, 2, 3) + image = {'id': 'c456eb30-91d7-4f43-8f46-2efd9eccd744', + 'name': 'fake-image-invalid-arch', + 'created_at': timestamp, + 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, + 'status': 'active', + 'is_public': False, + 'container_format': 'raw', + 'disk_format': 'raw', + 'size': '25165824', + 'properties': {'kernel_id': CONF.null_kernel, + 'ramdisk_id': CONF.null_kernel, + 'architecture': 'x64'}} + self.image_id = image_service.create(None, image)['id'] + self.flavor_id = self.api.get_flavors()[0]['id'] + + def test_boot_server_with_invalid_image_meta(self): + """Regression test for bug #1558866. + + Glance allows you to provide any architecture value for image meta + properties but nova validates the image metadata against the + nova.compute.arch.ALL values during the conversion to the ImageMeta + object. This test ensures we get a 400 back in that case rather than + a 500. + """ + server = dict(name='server1', + imageRef=self.image_id, + flavorRef=self.flavor_id) + ex = self.assertRaises(api_client.OpenStackApiException, + self.api.post_server, {'server': server}) + self.assertEqual(400, ex.response.status_code) diff --git a/nova/tests/unit/api/openstack/compute/test_flavors_extra_specs.py b/nova/tests/unit/api/openstack/compute/test_flavors_extra_specs.py index 783b3af289..b70a97a8ce 100644 --- a/nova/tests/unit/api/openstack/compute/test_flavors_extra_specs.py +++ b/nova/tests/unit/api/openstack/compute/test_flavors_extra_specs.py @@ -190,7 +190,8 @@ class FlavorsExtraSpecsTestV21(test.TestCase): def test_create_flavor_db_duplicate(self): def fake_instance_type_extra_specs_update_or_create(*args, **kwargs): - raise exception.FlavorExtraSpecUpdateCreateFailed(id=1, retries=5) + raise exception.FlavorExtraSpecUpdateCreateFailed(id='1', + retries=5) self.stubs.Set(nova.db, 'flavor_extra_specs_update_or_create', @@ -348,7 +349,8 @@ class FlavorsExtraSpecsTestV21(test.TestCase): def test_update_flavor_db_duplicate(self): def fake_instance_type_extra_specs_update_or_create(*args, **kwargs): - raise exception.FlavorExtraSpecUpdateCreateFailed(id=1, retries=5) + raise exception.FlavorExtraSpecUpdateCreateFailed(id='1', + retries=5) self.stubs.Set(nova.db, 'flavor_extra_specs_update_or_create', diff --git a/nova/tests/unit/api/openstack/compute/test_serversV21.py b/nova/tests/unit/api/openstack/compute/test_serversV21.py index 5b36a7828c..3ec79c3d78 100644 --- a/nova/tests/unit/api/openstack/compute/test_serversV21.py +++ b/nova/tests/unit/api/openstack/compute/test_serversV21.py @@ -2984,6 +2984,18 @@ class ServersControllerCreateTest(test.TestCase): self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, self.req, body=self.body) + def test_create_instance_without_personality_should_get_empty_list(self): + old_create = compute_api.API.create + del self.body['server']['personality'] + + def create(*args, **kwargs): + self.assertEqual([], kwargs['injected_files']) + return old_create(*args, **kwargs) + + self.stubs.Set(compute_api.API, 'create', create) + + self._test_create_instance() + def test_create_instance_with_extra_personality_arg(self): self.body['server']['personality'] = [ { diff --git a/nova/tests/unit/cmd/test_baseproxy.py b/nova/tests/unit/cmd/test_baseproxy.py index d3cec2fc12..38421f7a46 100644 --- a/nova/tests/unit/cmd/test_baseproxy.py +++ b/nova/tests/unit/cmd/test_baseproxy.py @@ -61,7 +61,7 @@ class BaseProxyTestCase(test.NoDBTestCase): mock_init.assert_called_once_with( listen_host='0.0.0.0', listen_port='6080', source_is_ipv6=False, verbose=False, cert='self.pem', key=None, ssl_only=False, - daemon=False, record=False, traffic=False, + daemon=False, record=None, traffic=False, web='/usr/share/spice-html5', file_only=True, RequestHandlerClass=websocketproxy.NovaProxyRequestHandler) mock_start.assert_called_once_with() diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 2f20e2323b..d7278bea2b 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -1207,7 +1207,7 @@ class _ComputeAPIUnitTestMixIn(object): self.context, fake_inst['uuid'], 'finished').AndReturn( fake_mig) compute_utils.reverse_upsize_quota_delta( - self.context, fake_mig).AndReturn('deltas') + self.context, fake_inst).AndReturn('deltas') resvs = ['resvs'] fake_quotas = objects.Quotas.from_reservations(self.context, resvs) @@ -1265,7 +1265,7 @@ class _ComputeAPIUnitTestMixIn(object): delta = ['delta'] compute_utils.reverse_upsize_quota_delta( - self.context, fake_mig).AndReturn(delta) + self.context, fake_inst).AndReturn(delta) resvs = ['resvs'] fake_quotas = objects.Quotas.from_reservations(self.context, resvs) compute_utils.reserve_quota_delta( diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 9dfb886f50..33b06709a3 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -1105,6 +1105,10 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase): exc = exception.DiskNotFound self._test_shutdown_instance_exception(exc) + def test_shutdown_instance_other_exception(self): + exc = Exception('some other exception') + self._test_shutdown_instance_exception(exc) + def _test_init_instance_retries_reboot(self, instance, reboot_type, return_power_state): instance.host = self.compute.host diff --git a/nova/tests/unit/compute/test_compute_utils.py b/nova/tests/unit/compute/test_compute_utils.py index 187d13232b..b6fc319c58 100644 --- a/nova/tests/unit/compute/test_compute_utils.py +++ b/nova/tests/unit/compute/test_compute_utils.py @@ -723,8 +723,7 @@ class ComputeUtilsQuotaDeltaTestCase(test.TestCase): deltas = compute_utils.downsize_quota_delta(self.context, inst) self.assertEqual(expected_deltas, deltas) - @mock.patch.object(objects.Flavor, 'get_by_id') - def test_reverse_quota_delta(self, mock_get_flavor): + def test_reverse_quota_delta(self): inst = create_instance(self.context, params=None) inst.old_flavor = flavors.get_flavor_by_name('m1.tiny') inst.new_flavor = flavors.get_flavor_by_name('m1.medium') @@ -735,20 +734,8 @@ class ComputeUtilsQuotaDeltaTestCase(test.TestCase): 'ram': -1 * (inst.new_flavor['memory_mb'] - inst.old_flavor['memory_mb']) } - updates = {'old_instance_type_id': inst.old_flavor['id'], - 'new_instance_type_id': inst.new_flavor['id']} - - fake_migration = test_migration.fake_db_migration(**updates) - - def _flavor_get_by_id(context, type_id): - if type_id == updates['old_instance_type_id']: - return inst.old_flavor - else: - return inst.new_flavor - mock_get_flavor.side_effect = _flavor_get_by_id - deltas = compute_utils.reverse_upsize_quota_delta(self.context, - fake_migration) + deltas = compute_utils.reverse_upsize_quota_delta(self.context, inst) self.assertEqual(expected_deltas, deltas) @mock.patch.object(objects.Quotas, 'reserve') diff --git a/nova/tests/unit/image/test_glance.py b/nova/tests/unit/image/test_glance.py index 15ab0674ad..59bed419ae 100644 --- a/nova/tests/unit/image/test_glance.py +++ b/nova/tests/unit/image/test_glance.py @@ -415,6 +415,45 @@ class TestGlanceClientWrapper(test.NoDBTestCase): ) sleep_mock.assert_called_once_with(1) + @mock.patch('random.shuffle') + @mock.patch('time.sleep') + @mock.patch('nova.image.glance._create_glance_client') + def test_retry_works_with_generators(self, create_client_mock, + sleep_mock, shuffle_mock): + def some_generator(exception): + if exception: + raise glanceclient.exc.CommunicationError('Boom!') + yield 'something' + + api_servers = [ + 'host1:9292', + 'https://host2:9293', + 'http://host3:9294' + ] + client_mock = mock.MagicMock() + images_mock = mock.MagicMock() + images_mock.list.side_effect = [ + some_generator(exception=True), + some_generator(exception=False), + ] + type(client_mock).images = mock.PropertyMock(return_value=images_mock) + create_client_mock.return_value = client_mock + + self.flags(num_retries=1, group='glance') + self.flags(api_servers=api_servers, group='glance') + + ctx = context.RequestContext('fake', 'fake') + client = glance.GlanceClientWrapper() + client.call(ctx, 1, 'list', 'meow') + + create_client_mock.assert_has_calls( + [ + mock.call(ctx, 'host1', 9292, False, 1), + mock.call(ctx, 'host2', 9293, True, 1), + ] + ) + sleep_mock.assert_called_once_with(1) + @mock.patch('oslo_service.sslutils.is_enabled') @mock.patch('glanceclient.Client') def test_create_glance_client_with_ssl(self, client_mock, diff --git a/nova/tests/unit/network/test_linux_net.py b/nova/tests/unit/network/test_linux_net.py index a75dc100be..26a531f2ca 100644 --- a/nova/tests/unit/network/test_linux_net.py +++ b/nova/tests/unit/network/test_linux_net.py @@ -34,6 +34,7 @@ from nova import db from nova import exception from nova.network import driver from nova.network import linux_net +from nova.network import model as network_model from nova import objects from nova import test from nova import utils @@ -1210,13 +1211,37 @@ class LinuxNetworkTestCase(test.NoDBTestCase): linux_net._set_device_mtu('fake-dev') ex.assert_has_calls(calls) - def _ovs_vif_port(self, calls): + def _ovs_vif_port(self, calls, interface_type=None): with mock.patch.object(utils, 'execute', return_value=('', '')) as ex: linux_net.create_ovs_vif_port('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', - 'fake-instance-uuid') + 'fake-instance-uuid', + interface_type=interface_type) ex.assert_has_calls(calls) + def test_ovs_vif_port_cmd(self): + expected = ['--', '--if-exists', + 'del-port', 'fake-dev', '--', 'add-port', + 'fake-bridge', 'fake-dev', + '--', 'set', 'Interface', 'fake-dev', + 'external-ids:iface-id=fake-iface-id', + 'external-ids:iface-status=active', + 'external-ids:attached-mac=fake-mac', + 'external-ids:vm-uuid=fake-instance-uuid' + ] + cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', + 'fake-iface-id', 'fake-mac', + 'fake-instance-uuid') + + self.assertEqual(expected, cmd) + + expected += ['type=fake-type'] + cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', + 'fake-iface-id', 'fake-mac', + 'fake-instance-uuid', + 'fake-type') + self.assertEqual(expected, cmd) + def test_ovs_vif_port(self): calls = [ mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists', @@ -1231,6 +1256,22 @@ class LinuxNetworkTestCase(test.NoDBTestCase): ] self._ovs_vif_port(calls) + @mock.patch.object(linux_net, '_ovs_vsctl') + @mock.patch.object(linux_net, '_create_ovs_vif_cmd') + @mock.patch.object(linux_net, '_set_device_mtu') + def test_ovs_vif_port_with_type_vhostuser(self, mock_set_device_mtu, + mock_create_cmd, mock_vsctl): + linux_net.create_ovs_vif_port( + 'fake-bridge', + 'fake-dev', 'fake-iface-id', 'fake-mac', + "fake-instance-uuid", mtu=1500, + interface_type=network_model.OVS_VHOSTUSER_INTERFACE_TYPE) + mock_create_cmd.assert_called_once_with('fake-bridge', + 'fake-dev', 'fake-iface-id', 'fake-mac', + "fake-instance-uuid", network_model.OVS_VHOSTUSER_INTERFACE_TYPE) + self.assertFalse(mock_set_device_mtu.called) + self.assertTrue(mock_vsctl.called) + def test_ovs_vif_port_with_mtu(self): self.flags(network_device_mtu=10000) calls = [ @@ -1371,16 +1412,6 @@ class LinuxNetworkTestCase(test.NoDBTestCase): self.assertEqual(2, len(executes)) self.mox.UnsetStubs() - def test_ovs_set_vhostuser_type(self): - calls = [ - mock.call('ovs-vsctl', '--timeout=120', '--', 'set', - 'Interface', 'fake-dev', 'type=dpdkvhostuser', - run_as_root=True) - ] - with mock.patch.object(utils, 'execute', return_value=('', '')) as ex: - linux_net.ovs_set_vhostuser_port_type('fake-dev') - ex.assert_has_calls(calls) - @mock.patch('os.path.exists', return_value=True) @mock.patch('nova.utils.execute') def test_remove_bridge(self, mock_execute, mock_exists): diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 7424cbb63f..295c672909 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -2391,7 +2391,8 @@ class TestNeutronv2(TestNeutronv2Base): 'binding:vif_type': vif_type, } fake_subnets = [model.Subnet(cidr='1.0.0.0/8')] - fake_nets = [{'id': 'net-id', 'name': 'foo', 'tenant_id': 'tenant'}] + fake_nets = [{'id': 'net-id', 'name': 'foo', 'tenant_id': 'tenant', + 'mtu': 9000}] api = neutronapi.API() self.mox.ReplayAll() neutronapi.get_client('fake') @@ -2401,6 +2402,7 @@ class TestNeutronv2(TestNeutronv2Base): self.assertEqual(net['id'], 'net-id') self.assertEqual(net['label'], 'foo') self.assertEqual(net.get_meta('tenant_id'), 'tenant') + self.assertEqual(9000, net.get_meta('mtu')) self.assertEqual(net.get_meta('injected'), CONF.flat_injected) return net, iid diff --git a/nova/tests/unit/objects/test_monitor_metric.py b/nova/tests/unit/objects/test_monitor_metric.py index 30cea2ded7..a1df3ef2f0 100644 --- a/nova/tests/unit/objects/test_monitor_metric.py +++ b/nova/tests/unit/objects/test_monitor_metric.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_serialization import jsonutils from oslo_utils import timeutils from nova import objects @@ -73,6 +74,17 @@ class _TestMonitorMetricObject(object): source='nova.virt.libvirt.driver') self.assertEqual(_monitor_numa_metric_spec, obj.to_dict()) + def test_conversion_in_monitor_metric_list_from_json(self): + spec_list = [_monitor_metric_spec, _monitor_metric_perc_spec] + metrics = objects.MonitorMetricList.from_json( + jsonutils.dumps(spec_list)) + for metric, spec in zip(metrics, spec_list): + exp = spec['value'] + if (spec['name'] in + objects.monitor_metric.FIELDS_REQUIRING_CONVERSION): + exp = spec['value'] * 100 + self.assertEqual(exp, metric.value) + class TestMonitorMetricObject(test_objects._LocalTest, _TestMonitorMetricObject): diff --git a/nova/tests/unit/virt/libvirt/test_vif.py b/nova/tests/unit/virt/libvirt/test_vif.py index 3bc8ab27f2..d7c18b615d 100644 --- a/nova/tests/unit/virt/libvirt/test_vif.py +++ b/nova/tests/unit/virt/libvirt/test_vif.py @@ -59,7 +59,7 @@ class LibvirtVifTestCase(test.NoDBTestCase): subnets=[subnet_bridge_4, subnet_bridge_6], bridge_interface='eth0', - vlan=99) + vlan=99, mtu=9000) vif_bridge = network_model.VIF(id='vif-xxx-yyy-zzz', address='ca:fe:de:ad:be:ef', @@ -89,7 +89,7 @@ class LibvirtVifTestCase(test.NoDBTestCase): subnets=[subnet_bridge_4, subnet_bridge_6], bridge_interface=None, - vlan=99) + vlan=99, mtu=1000) network_ivs = network_model.Network(id='network-id-xxx-yyy-zzz', bridge='br0', @@ -312,7 +312,7 @@ class LibvirtVifTestCase(test.NoDBTestCase): network_model.VIF_DETAILS_VHOSTUSER_SOCKET: '/tmp/usv-xxx-yyy-zzz', network_model.VIF_DETAILS_VHOSTUSER_OVS_PLUG: True}, - ovs_interfaceid='aaa-bbb-ccc' + ovs_interfaceid='aaa-bbb-ccc', mtu=1500 ) vif_vhostuser_no_path = network_model.VIF(id='vif-xxx-yyy-zzz', @@ -665,7 +665,7 @@ class LibvirtVifTestCase(test.NoDBTestCase): 'device_exists': [mock.call('qbrvif-xxx-yyy'), mock.call('qvovif-xxx-yyy')], '_create_veth_pair': [mock.call('qvbvif-xxx-yyy', - 'qvovif-xxx-yyy')], + 'qvovif-xxx-yyy', 1000)], 'execute': [mock.call('brctl', 'addbr', 'qbrvif-xxx-yyy', run_as_root=True), mock.call('brctl', 'setfd', 'qbrvif-xxx-yyy', 0, @@ -679,7 +679,8 @@ class LibvirtVifTestCase(test.NoDBTestCase): 'create_ovs_vif_port': [mock.call('br0', 'qvovif-xxx-yyy', 'aaa-bbb-ccc', 'ca:fe:de:ad:be:ef', - 'instance-uuid')] + 'instance-uuid', + 1000)] } # The disable_ipv6 call needs to be added in the middle, if required if ipv6_exists: @@ -799,7 +800,7 @@ class LibvirtVifTestCase(test.NoDBTestCase): 'device_exists': [mock.call('qbrvif-xxx-yyy'), mock.call('qvovif-xxx-yyy')], '_create_veth_pair': [mock.call('qvbvif-xxx-yyy', - 'qvovif-xxx-yyy')], + 'qvovif-xxx-yyy', None)], 'execute': [mock.call('brctl', 'addbr', 'qbrvif-xxx-yyy', run_as_root=True), mock.call('brctl', 'setfd', 'qbrvif-xxx-yyy', 0, @@ -1311,22 +1312,18 @@ class LibvirtVifTestCase(test.NoDBTestCase): def test_vhostuser_ovs_plug(self): calls = { - 'create_ovs_vif_port': [mock.call('br0', - 'usv-xxx-yyy-zzz', - 'aaa-bbb-ccc', - 'ca:fe:de:ad:be:ef', - 'instance-uuid')], - 'ovs_set_vhostuser_port_type': [mock.call('usv-xxx-yyy-zzz')] + 'create_ovs_vif_port': [ + mock.call( + 'br0', 'usv-xxx-yyy-zzz', 'aaa-bbb-ccc', + 'ca:fe:de:ad:be:ef', 'instance-uuid', 9000, + interface_type=network_model.OVS_VHOSTUSER_INTERFACE_TYPE + )] } - with contextlib.nested( - mock.patch.object(linux_net, 'create_ovs_vif_port'), - mock.patch.object(linux_net, 'ovs_set_vhostuser_port_type') - ) as (create_ovs_vif_port, ovs_set_vhostuser_port_type): + with mock.patch.object(linux_net, + 'create_ovs_vif_port') as create_ovs_vif_port: d = vif.LibvirtGenericVIFDriver() d.plug_vhostuser(self.instance, self.vif_vhostuser_ovs) create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port']) - ovs_set_vhostuser_port_type.assert_has_calls( - calls['ovs_set_vhostuser_port_type']) def test_vhostuser_ovs_unplug(self): calls = { diff --git a/nova/tests/unit/virt/vmwareapi/fake.py b/nova/tests/unit/virt/vmwareapi/fake.py index 72ecd1cec4..ad375f65ba 100644 --- a/nova/tests/unit/virt/vmwareapi/fake.py +++ b/nova/tests/unit/virt/vmwareapi/fake.py @@ -370,8 +370,9 @@ class VirtualIDEController(DataObject): class VirtualLsiLogicController(DataObject): """VirtualLsiLogicController class.""" - def __init__(self, key=0, scsiCtlrUnitNumber=0): + def __init__(self, key=0, scsiCtlrUnitNumber=0, busNumber=0): self.key = key + self.busNumber = busNumber self.scsiCtlrUnitNumber = scsiCtlrUnitNumber self.device = [] diff --git a/nova/tests/unit/virt/vmwareapi/test_vm_util.py b/nova/tests/unit/virt/vmwareapi/test_vm_util.py index d4930965c2..c20fa6de9d 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vm_util.py +++ b/nova/tests/unit/virt/vmwareapi/test_vm_util.py @@ -214,6 +214,13 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual("ns0:ParaVirtualSCSIController", config_spec.device.obj_name) + def test_create_controller_spec_with_specfic_bus_number(self): + # Test controller spec with specifc bus number rather default 0 + config_spec = vm_util.create_controller_spec(fake.FakeFactory(), -101, + adapter_type=constants.ADAPTER_TYPE_LSILOGICSAS, + bus_number=1) + self.assertEqual(1, config_spec.device.busNumber) + def _vmdk_path_and_adapter_type_devices(self, filename, parent=None): # Test the adapter_type returned for a lsiLogic sas controller controller_key = 1000 @@ -332,6 +339,27 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual([1], taken[201]) self.assertEqual([7], taken[1000]) + def test_get_bus_number_for_scsi_controller(self): + devices = [fake.VirtualLsiLogicController(1000, scsiCtlrUnitNumber=7, + busNumber=0), + fake.VirtualLsiLogicController(1002, scsiCtlrUnitNumber=7, + busNumber=2)] + bus_number = vm_util._get_bus_number_for_scsi_controller(devices) + self.assertEqual(1, bus_number) + + def test_get_bus_number_for_scsi_controller_buses_used_up(self): + devices = [fake.VirtualLsiLogicController(1000, scsiCtlrUnitNumber=7, + busNumber=0), + fake.VirtualLsiLogicController(1001, scsiCtlrUnitNumber=7, + busNumber=1), + fake.VirtualLsiLogicController(1002, scsiCtlrUnitNumber=7, + busNumber=2), + fake.VirtualLsiLogicController(1003, scsiCtlrUnitNumber=7, + busNumber=3)] + self.assertRaises(vexc.VMwareDriverException, + vm_util._get_bus_number_for_scsi_controller, + devices) + def test_allocate_controller_key_and_unit_number_ide_default(self): # Test that default IDE controllers are used when there is a free slot # on them @@ -386,6 +414,23 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual(8, unit_number) self.assertIsNone(controller_spec) + def test_allocate_controller_key_and_unit_number_scsi_new_controller(self): + # Test that we allocate on existing SCSI controller if there is a free + # slot on it + devices = [fake.VirtualLsiLogicController(1000, scsiCtlrUnitNumber=15)] + for unit_number in range(15): + disk = fake.VirtualDisk(1000, unit_number) + devices.append(disk) + factory = fake.FakeFactory() + (controller_key, unit_number, + controller_spec) = vm_util.allocate_controller_key_and_unit_number( + factory, + devices, + constants.DEFAULT_ADAPTER_TYPE) + self.assertEqual(-101, controller_key) + self.assertEqual(0, unit_number) + self.assertEqual(1, controller_spec.device.busNumber) + def test_get_vnc_config_spec(self): fake_factory = fake.FakeFactory() result = vm_util.get_vnc_config_spec(fake_factory, diff --git a/nova/tests/unit/volume/test_cinder.py b/nova/tests/unit/volume/test_cinder.py index 120a31e285..4334632268 100644 --- a/nova/tests/unit/volume/test_cinder.py +++ b/nova/tests/unit/volume/test_cinder.py @@ -293,6 +293,25 @@ class CinderApiTestCase(test.NoDBTestCase): mock_cinderclient.return_value.volumes. \ initialize_connection.assert_called_once_with(volume_id, connector) + @mock.patch('nova.volume.cinder.LOG') + @mock.patch('nova.volume.cinder.cinderclient') + def test_initialize_connection_exception_no_code( + self, mock_cinderclient, mock_log): + mock_cinderclient.return_value.volumes. \ + initialize_connection.side_effect = ( + cinder_exception.ClientException(500, "500")) + mock_cinderclient.return_value.volumes. \ + terminate_connection.side_effect = ( + test.TestingException) + + connector = {'host': 'fakehost1'} + self.assertRaises(cinder_exception.ClientException, + self.api.initialize_connection, + self.ctx, + 'id1', + connector) + self.assertIsNone(mock_log.error.call_args_list[1][0][1]['code']) + @mock.patch('nova.volume.cinder.cinderclient') def test_initialize_connection_rollback(self, mock_cinderclient): mock_cinderclient.return_value.volumes.\ diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 5fcf492580..54a445faef 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -512,13 +512,15 @@ class LibvirtGenericVIFDriver(object): check_exit_code=[0, 1]) if not linux_net.device_exists(v2_name): - linux_net._create_veth_pair(v1_name, v2_name) + mtu = vif['network'].get_meta('mtu') + linux_net._create_veth_pair(v1_name, v2_name, mtu) utils.execute('ip', 'link', 'set', br_name, 'up', run_as_root=True) utils.execute('brctl', 'addif', br_name, v1_name, run_as_root=True) if port == 'ovs': linux_net.create_ovs_vif_port(self.get_bridge_name(vif), v2_name, iface_id, - vif['address'], instance.uuid) + vif['address'], instance.uuid, + mtu) elif port == 'ivs': linux_net.create_ivs_vif_port(v2_name, iface_id, vif['address'], instance.uuid) @@ -660,7 +662,9 @@ class LibvirtGenericVIFDriver(object): dev = self.get_vif_devname(vif) mac = vif['details'].get(network_model.VIF_DETAILS_TAP_MAC_ADDRESS) linux_net.create_tap_dev(dev, mac) - linux_net._set_device_mtu(dev) + network = vif.get('network') + mtu = network.get_meta('mtu') if network else None + linux_net._set_device_mtu(dev, mtu) def plug_vhostuser(self, instance, vif): ovs_plug = vif['details'].get( @@ -670,10 +674,12 @@ class LibvirtGenericVIFDriver(object): iface_id = self.get_ovs_interfaceid(vif) port_name = os.path.basename( vif['details'][network_model.VIF_DETAILS_VHOSTUSER_SOCKET]) - linux_net.create_ovs_vif_port(self.get_bridge_name(vif), - port_name, iface_id, vif['address'], - instance.uuid) - linux_net.ovs_set_vhostuser_port_type(port_name) + mtu = vif['network'].get_meta('mtu') + linux_net.create_ovs_vif_port( + self.get_bridge_name(vif), + port_name, iface_id, vif['address'], + instance.uuid, mtu, + interface_type=network_model.OVS_VHOSTUSER_INTERFACE_TYPE) def plug_vrouter(self, instance, vif): """Plug into Contrail's network port diff --git a/nova/virt/vmwareapi/constants.py b/nova/virt/vmwareapi/constants.py index 7842b17d9e..5d76ba61b1 100644 --- a/nova/virt/vmwareapi/constants.py +++ b/nova/virt/vmwareapi/constants.py @@ -54,6 +54,9 @@ ADAPTER_TYPE_IDE = "ide" ADAPTER_TYPE_LSILOGICSAS = "lsiLogicsas" ADAPTER_TYPE_PARAVIRTUAL = "paraVirtual" +SCSI_ADAPTER_TYPES = [DEFAULT_ADAPTER_TYPE, ADAPTER_TYPE_LSILOGICSAS, + ADAPTER_TYPE_BUSLOGIC, ADAPTER_TYPE_PARAVIRTUAL] + SUPPORTED_FLAT_VARIANTS = ["thin", "preallocated", "thick", "eagerZeroedThick"] EXTENSION_KEY = 'org.openstack.compute' @@ -63,6 +66,9 @@ EXTENSION_TYPE_INSTANCE = 'instance' # One adapter has 16 slots but one reserved for controller SCSI_MAX_CONNECT_NUMBER = 15 +# The max number of SCSI adaptors that could be created on one instance. +SCSI_MAX_CONTROLLER_NUMBER = 4 + # This list was extracted from the installation iso image for ESX 6.0. # It is contained in s.v00, which is gzipped. The list was obtained by # searching for the string 'otherGuest' in the uncompressed contents of that diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 5e19cb0a97..bf0782fd23 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -355,7 +355,8 @@ def get_vm_resize_spec(client_factory, vcpus, memory_mb, extra_specs, def create_controller_spec(client_factory, key, - adapter_type=constants.DEFAULT_ADAPTER_TYPE): + adapter_type=constants.DEFAULT_ADAPTER_TYPE, + bus_number=0): """Builds a Config Spec for the LSI or Bus Logic Controller's addition which acts as the controller for the virtual hard disk to be attached to the VM. @@ -377,7 +378,7 @@ def create_controller_spec(client_factory, key, virtual_controller = client_factory.create( 'ns0:VirtualLsiLogicController') virtual_controller.key = key - virtual_controller.busNumber = 0 + virtual_controller.busNumber = bus_number virtual_controller.sharedBus = "noSharing" virtual_device_config.device = virtual_controller return virtual_device_config @@ -713,6 +714,19 @@ def _find_allocated_slots(devices): return taken +def _get_bus_number_for_scsi_controller(devices): + """Return usable bus number when create new SCSI controller.""" + # Every SCSI controller will take a unique bus number + taken = [dev.busNumber for dev in devices if _is_scsi_controller(dev)] + # The max bus number for SCSI controllers is 3 + for i in range(constants.SCSI_MAX_CONTROLLER_NUMBER): + if i not in taken: + return i + msg = _('Only %d SCSI controllers are allowed to be ' + 'created on this instance.') % constants.SCSI_MAX_CONTROLLER_NUMBER + raise vexc.VMwareDriverException(msg) + + def allocate_controller_key_and_unit_number(client_factory, devices, adapter_type): """This function inspects the current set of hardware devices and returns @@ -728,10 +742,7 @@ def allocate_controller_key_and_unit_number(client_factory, devices, if adapter_type == constants.ADAPTER_TYPE_IDE: ide_keys = [dev.key for dev in devices if _is_ide_controller(dev)] ret = _find_controller_slot(ide_keys, taken, 2) - elif adapter_type in [constants.DEFAULT_ADAPTER_TYPE, - constants.ADAPTER_TYPE_LSILOGICSAS, - constants.ADAPTER_TYPE_BUSLOGIC, - constants.ADAPTER_TYPE_PARAVIRTUAL]: + elif adapter_type in constants.SCSI_ADAPTER_TYPES: scsi_keys = [dev.key for dev in devices if _is_scsi_controller(dev)] ret = _find_controller_slot(scsi_keys, taken, 16) if ret: @@ -739,8 +750,14 @@ def allocate_controller_key_and_unit_number(client_factory, devices, # create new controller with the specified type and return its spec controller_key = -101 + + # Get free bus number for new SCSI controller. + bus_number = 0 + if adapter_type in constants.SCSI_ADAPTER_TYPES: + bus_number = _get_bus_number_for_scsi_controller(devices) + controller_spec = create_controller_spec(client_factory, controller_key, - adapter_type) + adapter_type, bus_number) return controller_key, 0, controller_spec diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py index 5a7c5aa106..e8748d1bc7 100644 --- a/nova/volume/cinder.py +++ b/nova/volume/cinder.py @@ -366,7 +366,8 @@ class API(object): {'vol': volume_id, 'host': connector.get('host'), 'msg': six.text_type(exc), - 'code': exc.code}) + 'code': ( + exc.code if hasattr(exc, 'code') else None)}) @translate_volume_exception def terminate_connection(self, context, volume_id, connector): diff --git a/releasenotes/notes/bug-1559026-47c3fa3468d66b07.yaml b/releasenotes/notes/bug-1559026-47c3fa3468d66b07.yaml new file mode 100644 index 0000000000..62543bf945 --- /dev/null +++ b/releasenotes/notes/bug-1559026-47c3fa3468d66b07.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - The ``record`` configuration option for the console proxy + services (like VNC, serial, spice) is changed from + boolean to string. It specifies the filename that will + be used for recording websocket frames. diff --git a/releasenotes/notes/vhost-user-mtu-23d0af36a8adfa56.yaml b/releasenotes/notes/vhost-user-mtu-23d0af36a8adfa56.yaml new file mode 100644 index 0000000000..642a7d2318 --- /dev/null +++ b/releasenotes/notes/vhost-user-mtu-23d0af36a8adfa56.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - When plugging virtual interfaces of type vhost-user the MTU value will + not be applied to the interface by nova. vhost-user ports exist only in + userspace and are not backed by kernel netdevs, for this reason it is + not possible to set the mtu on a vhost-user interface using standard + tools such as ifconfig or ip link. diff --git a/requirements.txt b/requirements.txt index 494b09d0aa..796cf5fe6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,8 +10,8 @@ eventlet>=0.17.4 Jinja2>=2.6 # BSD License (3 clause) keystonemiddleware!=2.4.0,>=2.0.0 lxml>=2.3 -Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7' -Routes!=2.0,>=1.12.3;python_version!='2.7' +Routes!=2.0,!=2.1,!=2.3.0,>=1.12.3;python_version=='2.7' # MIT +Routes!=2.0,!=2.3.0,>=1.12.3;python_version!='2.7' # MIT cryptography>=1.0 # Apache-2.0 WebOb>=1.2.3 greenlet>=0.3.2 @@ -22,7 +22,7 @@ sqlalchemy-migrate>=0.9.6 netaddr!=0.7.16,>=0.7.12 netifaces>=0.10.4 paramiko>=1.13.0 -Babel>=1.3 +Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD iso8601>=0.1.9 jsonschema!=2.5.0,<3.0.0,>=2.0.0 python-cinderclient>=1.3.1 |