Skip to content

Commit

Permalink
Merge pull request #51 from stackhpc/upstream/wallaby-2023-07-31
Browse files Browse the repository at this point in the history
Synchronise wallaby with upstream
  • Loading branch information
markgoddard authored Jul 31, 2023
2 parents 25e2d64 + 8fb6811 commit d70c81d
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 11 deletions.
9 changes: 8 additions & 1 deletion nova/api/openstack/compute/flavor_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,14 @@ def _remove_tenant_access(self, req, id, body):

vals = body['removeTenantAccess']
tenant = vals['tenant']
identity.verify_project_id(context, tenant)
# It doesn't really matter if project exists or not: we can delete
# it from flavor's access list in both cases.
try:
identity.verify_project_id(context, tenant)
except webob.exc.HTTPBadRequest as identity_exc:
msg = "Project ID %s is not a valid project." % tenant
if msg not in identity_exc.explanation:
raise

# NOTE(gibi): We have to load a flavor from the db here as
# flavor.remove_access() will try to emit a notification and that needs
Expand Down
22 changes: 13 additions & 9 deletions nova/api/openstack/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,27 @@ def verify_project_id(context, project_id):
"""verify that a project_id exists.
This attempts to verify that a project id exists. If it does not,
an HTTPBadRequest is emitted.
an HTTPBadRequest is emitted. Also HTTPBadRequest is emitted
if Keystone identity service version 3.0 is not found.
"""
adap = utils.get_ksa_adapter(
'identity', ksa_auth=context.get_auth_plugin(),
min_version=(3, 0), max_version=(3, 'latest'))

failure = webob.exc.HTTPBadRequest(
explanation=_("Project ID %s is not a valid project.") %
project_id)
try:
resp = adap.get('/projects/%s' % project_id)
except kse.EndpointNotFound:
LOG.error(
"Keystone identity service version 3.0 was not found. This might "
"be because your endpoint points to the v2.0 versioned endpoint "
"which is not supported. Please fix this.")
raise failure
"Keystone identity service version 3.0 was not found. This "
"might be caused by Nova misconfiguration or Keystone "
"problems.")
msg = _("Nova was unable to find Keystone service endpoint.")
# TODO(astupnik). It may be reasonable to switch to HTTP 503
# (HTTP Service Unavailable) instead of HTTP Bad Request here.
# If proper Keystone servie is inaccessible, then technially
# this is a server side error and not an error in Nova.
raise webob.exc.HTTPBadRequest(explanation=msg)
except kse.ClientException:
# something is wrong, like there isn't a keystone v3 endpoint,
# or nova isn't configured for the interface to talk to it;
Expand All @@ -57,7 +60,8 @@ def verify_project_id(context, project_id):
return True
elif resp.status_code == 404:
# we got access, and we know this project is not there
raise failure
msg = _("Project ID %s is not a valid project.") % project_id
raise webob.exc.HTTPBadRequest(explanation=msg)
elif resp.status_code == 403:
# we don't have enough permission to verify this, so default
# to "it's ok".
Expand Down
193 changes: 193 additions & 0 deletions nova/tests/functional/regressions/test_bug_1983753.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# 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 fixtures

from oslo_serialization import jsonutils

from nova.tests.functional.api import client
from nova.tests.functional.libvirt import test_pci_sriov_servers
from nova.tests.unit.virt.libvirt import fakelibvirt


class TestPciResize(test_pci_sriov_servers._PCIServersTestBase):
# these tests use multiple different configs so the whitelist is set by
# each testcase individually
PCI_PASSTHROUGH_WHITELIST = []
PCI_ALIAS = [
jsonutils.dumps(x)
for x in [
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
"name": "a-pci-dev",
},
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PF_PROD_ID,
"device_type": "type-PF",
"name": "a-pf",
},
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.VF_PROD_ID,
"device_type": "type-VF",
"name": "a-vf",
},
]
]

def setUp(self):
super().setUp()
self.useFixture(
fixtures.MockPatch(
'nova.virt.libvirt.driver.LibvirtDriver.'
'migrate_disk_and_power_off',
return_value='{}'
)
)
# These tests should not depend on the host's sysfs
self.useFixture(
fixtures.MockPatch('nova.pci.utils.is_physical_function'))
self.useFixture(
fixtures.MockPatch(
'nova.pci.utils.get_function_by_ifname',
return_value=(None, False)
)
)

def _test_resize_from_two_devs_to_one_dev(self, num_pci_on_dest):
# The fake libvirt will emulate on the host:
# * two type-PCI in slot 0, 1
compute1_pci_info = fakelibvirt.HostPCIDevicesInfo(num_pci=2)
# the config matches the PCI dev
compute1_device_spec = [
jsonutils.dumps(x)
for x in [
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
},
]
]
self.flags(group='pci', passthrough_whitelist=compute1_device_spec)
self.start_compute(hostname="compute1", pci_info=compute1_pci_info)
self.assertPCIDeviceCounts("compute1", total=2, free=2)

# create a server that requests two PCI devs
extra_spec = {"pci_passthrough:alias": "a-pci-dev:2"}
flavor_id = self._create_flavor(extra_spec=extra_spec)
server = self._create_server(flavor_id=flavor_id, networks=[])
self.assertPCIDeviceCounts("compute1", total=2, free=0)

# start another compute with a different amount of PCI dev available
compute2_pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=num_pci_on_dest)
# the config matches the PCI dev
compute2_device_spec = [
jsonutils.dumps(x)
for x in [
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
},
]
]
self.flags(group='pci', passthrough_whitelist=compute2_device_spec)
self.start_compute(hostname="compute2", pci_info=compute2_pci_info)
self.assertPCIDeviceCounts(
"compute2", total=num_pci_on_dest, free=num_pci_on_dest)

# resize the server to request only one PCI dev instead of the current
# two. This should fit to compute2 having at least one dev
extra_spec = {"pci_passthrough:alias": "a-pci-dev:1"}
flavor_id = self._create_flavor(extra_spec=extra_spec)
self._resize_server(server, flavor_id=flavor_id)
self._confirm_resize(server)
self.assertPCIDeviceCounts("compute1", total=2, free=2)
self.assertPCIDeviceCounts(
"compute2", total=num_pci_on_dest, free=num_pci_on_dest - 1)

def test_resize_from_two_devs_to_one_dev_dest_has_two_devs(self):
# this works
self._test_resize_from_two_devs_to_one_dev(num_pci_on_dest=2)

def test_resize_from_two_devs_to_one_dev_dest_has_one_dev(self):
# This is bug 1983753 as nova uses the old InstancePciRequest during
# the scheduling and therefore tries to find a compute with two PCI
# devs even though the flavor only requests one.
ex = self.assertRaises(
client.OpenStackApiException,
self._test_resize_from_two_devs_to_one_dev,
num_pci_on_dest=1
)
self.assertIn('nova.exception.NoValidHost', str(ex))

def test_resize_from_vf_to_pf(self):
# The fake libvirt will emulate on the host:
# * one type-PF in slot 0 with one VF
compute1_pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pfs=1, num_vfs=1)
# the config matches only the VF
compute1_device_spec = [
jsonutils.dumps(x)
for x in [
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.VF_PROD_ID,
},
]
]
self.flags(group='pci', passthrough_whitelist=compute1_device_spec)
self.start_compute(hostname="compute1", pci_info=compute1_pci_info)
self.assertPCIDeviceCounts("compute1", total=1, free=1)

# create a server that requests one Vf
extra_spec = {"pci_passthrough:alias": "a-vf:1"}
flavor_id = self._create_flavor(extra_spec=extra_spec)
server = self._create_server(flavor_id=flavor_id, networks=[])
self.assertPCIDeviceCounts("compute1", total=1, free=0)

# start another compute with a single PF dev available
# The fake libvirt will emulate on the host:
# * one type-PF in slot 0 with 1 VF
compute2_pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pfs=1, num_vfs=1)
# the config matches the PF dev but not the VF
compute2_device_spec = [
jsonutils.dumps(x)
for x in [
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PF_PROD_ID,
},
]
]
self.flags(group='pci', passthrough_whitelist=compute2_device_spec)
self.start_compute(hostname="compute2", pci_info=compute2_pci_info)
self.assertPCIDeviceCounts("compute2", total=1, free=1)

# resize the server to request on PF dev instead of the current VF
# dev. This should fit to compute2 having exactly one PF dev.
extra_spec = {"pci_passthrough:alias": "a-pf:1"}
flavor_id = self._create_flavor(extra_spec=extra_spec)
# This is bug 1983753 as nova uses the old InstancePciRequest during
# the scheduling and therefore tries to find a compute with a VF dev
# even though the flavor only requests a PF dev.
ex = self.assertRaises(
client.OpenStackApiException,
self._resize_server,
server,
flavor_id=flavor_id,
)
self.assertIn('nova.exception.NoValidHost', str(ex))
25 changes: 24 additions & 1 deletion nova/tests/unit/api/openstack/compute/test_flavor_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,14 +351,37 @@ def test_add_tenant_access_with_invalid_tenant(self, mock_verify):
mock_verify.assert_called_once_with(
req.environ['nova.context'], 'proj2')

@mock.patch('nova.objects.Flavor.remove_access')
@mock.patch('nova.api.openstack.identity.verify_project_id',
side_effect=exc.HTTPBadRequest(
explanation="Project ID proj2 is not a valid project."))
def test_remove_tenant_access_with_invalid_tenant(self, mock_verify):
def test_remove_tenant_access_with_invalid_tenant(self,
mock_verify,
mock_remove_access):
"""Tests the case that the tenant does not exist in Keystone."""
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'removeTenantAccess': {'tenant': 'proj2'}}

self.flavor_action_controller._remove_tenant_access(
req, '2', body=body)
mock_verify.assert_called_once_with(
req.environ['nova.context'], 'proj2')
mock_remove_access.assert_called_once_with('proj2')

@mock.patch('nova.api.openstack.identity.verify_project_id',
side_effect=exc.HTTPBadRequest(
explanation="Nova was unable to find Keystone "
"service endpoint."))
def test_remove_tenant_access_missing_keystone_endpoint(self,
mock_verify):
"""Tests the case that Keystone identity service endpoint
version 3.0 was not found.
"""
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'removeTenantAccess': {'tenant': 'proj2'}}

self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._remove_tenant_access,
req, '2', body=body)
Expand Down

0 comments on commit d70c81d

Please sign in to comment.