diff --git a/nova/compute/api.py b/nova/compute/api.py index 5223b29b699..47c6b27d855 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -4240,7 +4240,7 @@ def resize(self, context, instance, flavor_id=None, clean_shutdown=True, new_flavor = current_flavor else: new_flavor = flavors.get_flavor_by_flavor_id( - flavor_id, read_deleted="no") + flavor_id, ctxt=context, read_deleted="no") # NOTE(wenping): We use this instead of the 'block_accelerator' # decorator since the operation can differ depending on args, # and for resize we have two flavors to worry about, we should diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index cf8df5bb761..0be5f460dfe 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -2109,6 +2109,43 @@ def test_resize_not_enough_resource(self): self._delete_and_check_allocations(server) + def test_resize_with_private_flavor(self): + """Ensure a non-admin user cannot resize to a private flavor that + has not been granted access to their project. + """ + source_hostname = self.compute1.host + + server = self._boot_and_check_allocations( + self.flavor1, source_hostname) + + # Create a private flavor and grant access to a different project. + private_flavor_body = {'flavor': { + 'name': 'private_flavor', + 'ram': 1024, + 'vcpus': 1, + 'disk': 10, + 'os-flavor-access:is_public': False, + }} + private_flavor = self.admin_api.post_flavor(private_flavor_body) + self.admin_api.api_post( + 'flavors/%s/action' % private_flavor['id'], + {'addTenantAccess': {'tenant': 'other-project'}}) + + # Use a non-admin API client to attempt the resize. + non_admin_api = self.api_fixture.api + non_admin_api.microversion = self.microversion + + resize_req = { + 'resize': { + 'flavorRef': private_flavor['id'] + } + } + ex = self.assertRaises( + client.OpenStackApiException, + non_admin_api.post_server_action, + server['id'], resize_req) + self.assertEqual(400, ex.response.status_code) + def test_resize_delete_while_verify(self): """Test scenario where the server is deleted while in the VERIFY_RESIZE state and ensures the allocations are properly diff --git a/nova/tests/unit/compute/test_api.py b/nova/tests/unit/compute/test_api.py index 8d015aa156d..8cc4d4d26da 100644 --- a/nova/tests/unit/compute/test_api.py +++ b/nova/tests/unit/compute/test_api.py @@ -2172,6 +2172,7 @@ def _check_state(expected_task_state=None): if flavor_id_passed: mock_get_flavor.assert_called_once_with('new-flavor-id', + ctxt=self.context, read_deleted='no') if not (flavor_id_passed and same_flavor): @@ -2380,7 +2381,9 @@ def test_resize_invalid_flavor_fails(self, mock_get_flavor, mock_count, self.assertRaises(exception.FlavorNotFound, self.compute_api.resize, self.context, fake_inst, flavor_id='flavor-id') - mock_get_flavor.assert_called_once_with('flavor-id', read_deleted='no') + mock_get_flavor.assert_called_once_with('flavor-id', + ctxt=self.context, + read_deleted='no') # Should never reach these. mock_count.assert_not_called() mock_limit.assert_not_called() @@ -2413,7 +2416,8 @@ def test_resize_vol_backed_smaller_min_ram(self, mock_get_flavor, self.assertRaises(exception.FlavorMemoryTooSmall, self.compute_api.resize, self.context, fake_inst, flavor_id=new_flavor.id) - mock_get_flavor.assert_called_once_with(200, read_deleted='no') + mock_get_flavor.assert_called_once_with(200, ctxt=self.context, + read_deleted='no') # Should never reach these. mock_count.assert_not_called() mock_limit.assert_not_called() @@ -2441,7 +2445,9 @@ def test_resize_disabled_flavor_fails(self, mock_get_flavor, mock_count, self.assertRaises(exception.FlavorNotFound, self.compute_api.resize, self.context, fake_inst, flavor_id='flavor-id') - mock_get_flavor.assert_called_once_with('flavor-id', read_deleted='no') + mock_get_flavor.assert_called_once_with('flavor-id', + ctxt=self.context, + read_deleted='no') # Should never reach these. mock_count.assert_not_called() mock_limit.assert_not_called() @@ -2540,7 +2546,9 @@ def test_resize_quota_exceeds_fails(self, mock_get_flavor, mock_upsize, fake_inst, flavor_id='flavor-id') mock_save.assert_not_called() - mock_get_flavor.assert_called_once_with('flavor-id', read_deleted='no') + mock_get_flavor.assert_called_once_with('flavor-id', + ctxt=self.context, + read_deleted='no') mock_upsize.assert_called_once_with(test.MatchType(objects.Flavor), test.MatchType(objects.Flavor)) # mock.ANY might be 'instances', 'cores', or 'ram' @@ -2616,6 +2624,7 @@ def test_resize_instance_quota_exceeds_with_multiple_resources( self.assertEqual('1, 512', e.kwargs['used']) self.assertEqual('1, 512', e.kwargs['allowed']) mock_get_flavor.assert_called_once_with('fake_flavor_id', + ctxt=self.context, read_deleted="no") else: self.fail("Exception not raised") @@ -2642,6 +2651,7 @@ def test_resize_instance_quota_exceeds_with_multiple_resources_ul( 'fake_flavor_id') mock_get_flavor.assert_called_once_with('fake_flavor_id', + ctxt=self.context, read_deleted="no") mock_enforce.assert_called_once_with( self.context, "fake", mock_get_flavor.return_value, False, 1, 1)