Merge "Use nova-consoleauth only if workaround enabled"

This commit is contained in:
Zuul 2018-10-15 17:36:29 +00:00 committed by Gerrit Code Review
commit 5e8af9fc09
7 changed files with 238 additions and 101 deletions

View File

@ -16,10 +16,15 @@
import webob
from nova.api.openstack import wsgi
import nova.conf
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import context as nova_context
from nova.i18n import _
from nova import objects
from nova.policies import console_auth_tokens as cat_policies
CONF = nova.conf.CONF
class ConsoleAuthTokensController(wsgi.Controller):
def __init__(self, *args, **kwargs):
@ -36,7 +41,24 @@ class ConsoleAuthTokensController(wsgi.Controller):
msg = _("token not provided")
raise webob.exc.HTTPBadRequest(explanation=msg)
connect_info = self._consoleauth_rpcapi.check_token(context, token)
connect_info = None
if CONF.workarounds.enable_consoleauth:
connect_info = self._consoleauth_rpcapi.check_token(context, token)
else:
results = nova_context.scatter_gather_skip_cell0(
context, objects.ConsoleAuthToken.validate, token)
# NOTE(melwitt): Console token auths are stored in cell databases,
# but with only the token as a request param, we can't know which
# cell database contains the token's corresponding connection info.
# So, we must query all cells for the info and we can break the
# loop as soon as we find a result because the token is associated
# with one instance, which can only be in one cell.
for result in results.values():
if result not in (nova_context.did_not_respond_sentinel,
nova_context.raised_exception_sentinel):
connect_info = result.to_dict()
break
if not connect_info:
raise webob.exc.HTTPNotFound(explanation=_("Token not found"))

View File

@ -1967,12 +1967,11 @@ class API(base.Base):
# NOTE(dtp): cells.enable = False means "use cells v2".
# Run everywhere except v1 compute cells.
if not CONF.cells.enable or self.cell_type == 'api':
# TODO(melwitt): In Rocky, we store console authorizations
# in both the consoleauth service and the database while
# we convert to using the database. Remove the consoleauth
# line below when authorizations are no longer being
# stored in consoleauth, in Stein.
if (not CONF.cells.enable and CONF.workarounds.enable_consoleauth
) or self.cell_type == 'api':
# TODO(melwitt): Remove the conditions for running this line
# with cells v2, when consoleauth is no longer being used by
# cells v2, in Stein.
self.consoleauth_rpcapi.delete_tokens_for_instance(
context, instance.uuid)
@ -3762,11 +3761,12 @@ class API(base.Base):
# console authorization in the database in the above method.
# The following will be removed when everything has been
# converted to use the database, in Stein.
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
if CONF.workarounds.enable_consoleauth:
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
return {'url': connect_info['access_url']}
@ -3788,11 +3788,12 @@ class API(base.Base):
# console authorization in the database in the above method.
# The following will be removed when everything has been
# converted to use the database, in Stein.
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
if CONF.workarounds.enable_consoleauth:
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
return {'url': connect_info['access_url']}
@ -3814,11 +3815,12 @@ class API(base.Base):
# console authorization in the database in the above method.
# The following will be removed when everything has been
# converted to use the database, in Stein.
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
if CONF.workarounds.enable_consoleauth:
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
return {'url': connect_info['access_url']}
@ -3841,11 +3843,12 @@ class API(base.Base):
# console authorization in the database in the above method.
# The following will be removed when everything has been
# converted to use the database, in Stein.
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
if CONF.workarounds.enable_consoleauth:
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
return {'url': connect_info['access_url']}
@check_instance_host
@ -3866,11 +3869,12 @@ class API(base.Base):
# console authorization in the database in the above method.
# The following will be removed when everything has been
# converted to use the database, in Stein.
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
if CONF.workarounds.enable_consoleauth:
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type,
connect_info['host'], connect_info['port'],
connect_info['internal_access_path'], instance.uuid,
access_url=connect_info['access_url'])
return {'url': connect_info['access_url']}
@check_instance_host
@ -4395,13 +4399,14 @@ class API(base.Base):
self._record_action_start(context, instance,
instance_actions.LIVE_MIGRATION)
# TODO(melwitt): In Rocky, we store console authorizations
# TODO(melwitt): In Rocky, we optionally store console authorizations
# in both the consoleauth service and the database while
# we convert to using the database. Remove the consoleauth
# line below when authorizations are no longer being
# stored in consoleauth, in Stein.
self.consoleauth_rpcapi.delete_tokens_for_instance(
context, instance.uuid)
# we convert to using the database. Remove the condition for running
# this line with cells v2, when consoleauth is no longer being used by
# cells v2, in Stein.
if CONF.cells.enable or CONF.workarounds.enable_consoleauth:
self.consoleauth_rpcapi.delete_tokens_for_instance(
context, instance.uuid)
try:
request_spec = objects.RequestSpec.get_by_instance_uuid(

View File

@ -113,25 +113,6 @@ class NovaProxyRequestHandlerBase(object):
return origin_proto in expected_protos
@staticmethod
def _console_auth_token_obj_to_dict(obj):
"""Convert to a dict representation."""
# NOTE(PaulMurray) For compatibility while there is code that
# expects the dict representation returned by consoleauth.
# TODO(PaulMurray) Remove this function when the code no
# longer expects the consoleauth dict representation
connect_info = {}
connect_info['token'] = obj.token,
connect_info['instance_uuid'] = obj.instance_uuid
connect_info['console_type'] = obj.console_type
connect_info['host'] = obj.host
connect_info['port'] = obj.port
if 'internal_access_path' in obj:
connect_info['internal_access_path'] = obj.internal_access_path
if 'access_url_base' in obj:
connect_info['access_url'] = obj.access_url
return connect_info
def _check_console_port(self, ctxt, instance_uuid, port, console_type):
try:
@ -155,8 +136,7 @@ class NovaProxyRequestHandlerBase(object):
# NOTE(PaulMurray) ConsoleAuthToken.validate validates the token.
# We call the compute manager directly to check the console port
# is correct.
connect_info = self._console_auth_token_obj_to_dict(
objects.ConsoleAuthToken.validate(ctxt, token))
connect_info = objects.ConsoleAuthToken.validate(ctxt, token).to_dict()
valid_port = self._check_console_port(
ctxt, connect_info['instance_uuid'], connect_info['port'],

View File

@ -74,6 +74,24 @@ class ConsoleAuthToken(base.NovaTimestampObject, base.NovaObject):
obj.obj_reset_changes()
return obj
def to_dict(self):
"""Convert to a dict representation."""
# NOTE(PaulMurray) For compatibility while there is code that
# expects the dict representation returned by consoleauth.
# TODO(PaulMurray) Remove this function when the code no
# longer expects the consoleauth dict representation
connect_info = {}
connect_info['token'] = self.token,
connect_info['instance_uuid'] = self.instance_uuid
connect_info['console_type'] = self.console_type
connect_info['host'] = self.host
connect_info['port'] = self.port
if 'internal_access_path' in self:
connect_info['internal_access_path'] = self.internal_access_path
if 'access_url_base' in self:
connect_info['access_url'] = self.access_url
return connect_info
@base.remotable
def authorize(self, ttl):
"""Authorise the console token and store in the database.

View File

@ -13,6 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import ddt
import mock
import webob
@ -20,58 +23,115 @@ from nova.api.openstack import api_version_request
from nova.api.openstack.compute import console_auth_tokens \
as console_auth_tokens_v21
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import exception
from nova import objects
from nova import test
from nova.tests.unit.api.openstack import fakes
@ddt.ddt
class ConsoleAuthTokensExtensionTestV21(test.NoDBTestCase):
controller_class = console_auth_tokens_v21
_EXPECTED_OUTPUT = {'console': {'instance_uuid': fakes.FAKE_UUID,
'host': 'fake_host',
'port': 'fake_port',
'port': '1234',
'internal_access_path':
'fake_access_path'}}
# The database backend returns a ConsoleAuthToken.to_dict() and o.vo
# StringField are unicode. And the port is an IntegerField.
_EXPECTED_OUTPUT_DB = copy.deepcopy(_EXPECTED_OUTPUT)
_EXPECTED_OUTPUT_DB['console'].update(
{'host': u'fake_host', 'port': 1234,
'internal_access_path': u'fake_access_path'})
def setUp(self):
super(ConsoleAuthTokensExtensionTestV21, self).setUp()
self.controller = self.controller_class.ConsoleAuthTokensController()
self.req = fakes.HTTPRequest.blank('', use_admin_context=True)
self.context = self.req.environ['nova.context']
@ddt.data(True, False)
@mock.patch('nova.objects.ConsoleAuthToken.validate',
return_value=objects.ConsoleAuthToken(
instance_uuid=fakes.FAKE_UUID, host='fake_host',
port='1234', internal_access_path='fake_access_path',
console_type='rdp-html5', token=fakes.FAKE_UUID))
@mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
return_value={
'instance_uuid': fakes.FAKE_UUID,
'host': 'fake_host',
'port': 'fake_port',
'port': '1234',
'internal_access_path': 'fake_access_path',
'console_type': 'rdp-html5'})
def test_get_console_connect_info(self, mock_check_token):
def test_get_console_connect_info(self, enable_consoleauth,
mock_check_token, mock_validate):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
output = self.controller.show(self.req, fakes.FAKE_UUID)
self.assertEqual(self._EXPECTED_OUTPUT, output)
mock_check_token.assert_called_once_with(self.context, fakes.FAKE_UUID)
if enable_consoleauth:
self.assertEqual(self._EXPECTED_OUTPUT, output)
mock_check_token.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_validate.assert_not_called()
else:
self.assertEqual(self._EXPECTED_OUTPUT_DB, output)
mock_validate.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_check_token.assert_not_called()
@ddt.data(True, False)
@mock.patch('nova.objects.ConsoleAuthToken.validate',
side_effect=exception.InvalidToken(token='***'))
@mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
return_value=None)
def test_get_console_connect_info_token_not_found(self, mock_check_token):
def test_get_console_connect_info_token_not_found(self, enable_consoleauth,
mock_check_token,
mock_validate):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, self.req, fakes.FAKE_UUID)
mock_check_token.assert_called_once_with(self.context, fakes.FAKE_UUID)
if enable_consoleauth:
mock_check_token.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_validate.assert_not_called()
else:
mock_validate.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_check_token.assert_not_called()
@ddt.data(True, False)
@mock.patch('nova.objects.ConsoleAuthToken.validate',
return_value=objects.ConsoleAuthToken(
instance_uuid=fakes.FAKE_UUID, host='fake_host',
port='1234', internal_access_path='fake_access_path',
console_type='unauthorized_console_type',
token=fakes.FAKE_UUID))
@mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token',
return_value={
'instance_uuid': fakes.FAKE_UUID,
'host': 'fake_host',
'port': 'fake_port',
'port': '1234',
'internal_access_path': 'fake_access_path',
'console_type': 'unauthorized_console_type'})
def test_get_console_connect_info_nonrdp_console_type(self,
mock_check_token):
enable_consoleauth,
mock_check_token,
mock_validate):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self.assertRaises(webob.exc.HTTPUnauthorized,
self.controller.show, self.req, fakes.FAKE_UUID)
mock_check_token.assert_called_once_with(self.context, fakes.FAKE_UUID)
if enable_consoleauth:
mock_check_token.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_validate.assert_not_called()
else:
mock_validate.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_check_token.assert_not_called()
@ddt.ddt
class ConsoleAuthTokensExtensionTestV231(ConsoleAuthTokensExtensionTestV21):
def setUp(self):
@ -79,13 +139,30 @@ class ConsoleAuthTokensExtensionTestV231(ConsoleAuthTokensExtensionTestV21):
self.req.api_version_request = api_version_request.APIVersionRequest(
'2.31')
@ddt.data(True, False)
@mock.patch('nova.objects.ConsoleAuthToken.validate')
@mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token')
def test_get_console_connect_info_nonrdp_console_type(self, mock_check):
def test_get_console_connect_info_nonrdp_console_type(self,
enable_consoleauth,
mock_check,
mock_validate):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
mock_validate.return_value = objects.ConsoleAuthToken(
instance_uuid=fakes.FAKE_UUID, host='fake_host', port='1234',
internal_access_path='fake_access_path', console_type='webmks',
token=fakes.FAKE_UUID)
mock_check.return_value = {'instance_uuid': fakes.FAKE_UUID,
'host': 'fake_host',
'port': 'fake_port',
'port': '1234',
'internal_access_path': 'fake_access_path',
'console_type': 'webmks'}
output = self.controller.show(self.req, fakes.FAKE_UUID)
self.assertEqual(self._EXPECTED_OUTPUT, output)
mock_check.assert_called_once_with(self.context, fakes.FAKE_UUID)
if enable_consoleauth:
self.assertEqual(self._EXPECTED_OUTPUT, output)
mock_check.assert_called_once_with(self.context, fakes.FAKE_UUID)
mock_validate.assert_not_called()
else:
self.assertEqual(self._EXPECTED_OUTPUT_DB, output)
mock_validate.assert_called_once_with(self.context,
fakes.FAKE_UUID)
mock_check.assert_not_called()

View File

@ -9925,10 +9925,12 @@ class ComputeAPITestCase(BaseTestCase):
self.assertRaises(exception.InvalidVolume,
self.compute_api.rescue, self.context, instance)
@ddt.data(True, False)
@mock.patch.object(compute_rpcapi.ComputeAPI, 'get_vnc_console')
@mock.patch.object(compute_api.consoleauth_rpcapi.ConsoleAuthAPI,
'authorize_console')
def test_vnc_console(self, mock_auth, mock_get):
def test_vnc_console(self, enable_consoleauth, mock_auth, mock_get):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
# Make sure we can a vnc console for an instance.
fake_instance = self._fake_instance(
@ -9951,11 +9953,14 @@ class ComputeAPITestCase(BaseTestCase):
mock_get.assert_called_once_with(
self.context, instance=fake_instance,
console_type=fake_console_type)
mock_auth.assert_called_once_with(
self.context, 'fake_token', fake_console_type, 'fake_console_host',
'fake_console_port', 'fake_access_path',
'f3000000-0000-0000-0000-000000000000',
access_url='fake_console_url')
if enable_consoleauth or CONF.cells.enable:
mock_auth.assert_called_once_with(
self.context, 'fake_token', fake_console_type,
'fake_console_host', 'fake_console_port', 'fake_access_path',
'f3000000-0000-0000-0000-000000000000',
access_url='fake_console_url')
else:
mock_auth.assert_not_called()
def test_get_vnc_console_no_host(self):
instance = self._create_fake_instance_obj(params={'host': ''})
@ -9964,10 +9969,12 @@ class ComputeAPITestCase(BaseTestCase):
self.compute_api.get_vnc_console,
self.context, instance, 'novnc')
@ddt.data(True, False)
@mock.patch.object(compute_api.consoleauth_rpcapi.ConsoleAuthAPI,
'authorize_console')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'get_spice_console')
def test_spice_console(self, mock_spice, mock_auth):
def test_spice_console(self, enable_consoleauth, mock_spice, mock_auth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
# Make sure we can a spice console for an instance.
fake_instance = self._fake_instance(
@ -9990,11 +9997,14 @@ class ComputeAPITestCase(BaseTestCase):
mock_spice.assert_called_once_with(self.context,
instance=fake_instance,
console_type=fake_console_type)
mock_auth.assert_called_once_with(
self.context, 'fake_token', fake_console_type, 'fake_console_host',
'fake_console_port', 'fake_access_path',
'f3000000-0000-0000-0000-000000000000',
access_url='fake_console_url')
if enable_consoleauth or CONF.cells.enable:
mock_auth.assert_called_once_with(
self.context, 'fake_token', fake_console_type,
'fake_console_host', 'fake_console_port', 'fake_access_path',
'f3000000-0000-0000-0000-000000000000',
access_url='fake_console_url')
else:
mock_auth.assert_not_called()
def test_get_spice_console_no_host(self):
instance = self._create_fake_instance_obj(params={'host': ''})
@ -10022,10 +10032,12 @@ class ComputeAPITestCase(BaseTestCase):
getattr(self.compute_api, 'get_%s_console' % console_type),
self.context, instance, console_type)
@ddt.data(True, False)
@mock.patch.object(compute_api.consoleauth_rpcapi.ConsoleAuthAPI,
'authorize_console')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'get_rdp_console')
def test_rdp_console(self, mock_rdp, mock_auth):
def test_rdp_console(self, enable_consoleauth, mock_rdp, mock_auth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
# Make sure we can a rdp console for an instance.
fake_instance = self._fake_instance({
'uuid': 'f3000000-0000-0000-0000-000000000000',
@ -10046,11 +10058,14 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(console, {'url': 'fake_console_url'})
mock_rdp.assert_called_once_with(self.context, instance=fake_instance,
console_type=fake_console_type)
mock_auth.assert_called_once_with(
self.context, 'fake_token', fake_console_type, 'fake_console_host',
'fake_console_port', 'fake_access_path',
'f3000000-0000-0000-0000-000000000000',
access_url='fake_console_url')
if enable_consoleauth or CONF.cells.enable:
mock_auth.assert_called_once_with(
self.context, 'fake_token', fake_console_type,
'fake_console_host', 'fake_console_port', 'fake_access_path',
'f3000000-0000-0000-0000-000000000000',
access_url='fake_console_url')
else:
mock_auth.assert_not_called()
def test_get_rdp_console_no_host(self):
instance = self._create_fake_instance_obj(params={'host': ''})
@ -11164,8 +11179,11 @@ class ComputeAPITestCase(BaseTestCase):
disk_over_commit=True,
request_spec=fake_spec, async_=False)
delete_tokens_for_instance.assert_called_once_with(
self.context, instance.uuid)
if CONF.workarounds.enable_consoleauth or CONF.cells.enable:
delete_tokens_for_instance.assert_called_once_with(
self.context, instance.uuid)
else:
delete_tokens_for_instance.assert_not_called()
do_test()
instance.refresh()
@ -11177,13 +11195,19 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual('fake_dest_host', req_dest.host)
self.assertEqual('fake_dest_node', req_dest.node)
def test_live_migrate(self):
@ddt.data(True, False)
def test_live_migrate(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self._test_live_migrate()
def test_live_migrate_with_not_forced_host(self):
@ddt.data(True, False)
def test_live_migrate_with_not_forced_host(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self._test_live_migrate(force=False)
def test_live_migrate_with_forced_host(self):
@ddt.data(True, False)
def test_live_migrate_with_forced_host(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self._test_live_migrate(force=True)
def test_fail_live_migrate_with_non_existing_destination(self):

View File

@ -1200,8 +1200,11 @@ class _ComputeAPIUnitTestMixIn(object):
mock_terminate.assert_called_once_with(
self.context, inst, [], delete_type=delete_type)
if self.cell_type is None or self.cell_type == 'api':
if ((self.cell_type is None and CONF.workarounds.enable_consoleauth)
or self.cell_type == 'api'):
mock_del_token.assert_called_once_with(self.context, instance_uuid)
else:
mock_del_token.assert_not_called()
if is_shelved:
mock_image_delete.assert_called_once_with(self.context,
@ -1227,7 +1230,9 @@ class _ComputeAPIUnitTestMixIn(object):
task_state=task_states.RESIZE_FINISH,
old_flavor=old_flavor)
def test_delete_in_resized(self):
@ddt.data(True, False)
def test_delete_in_resized(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self._test_delete('delete', vm_state=vm_states.RESIZED)
def test_delete_shelved(self):
@ -1236,7 +1241,9 @@ class _ComputeAPIUnitTestMixIn(object):
vm_state=vm_states.SHELVED,
system_metadata=fake_sys_meta)
def test_delete_shelved_offloaded(self):
@ddt.data(True, False)
def test_delete_shelved_offloaded(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE}
self._test_delete('delete',
vm_state=vm_states.SHELVED_OFFLOADED,
@ -1254,7 +1261,9 @@ class _ComputeAPIUnitTestMixIn(object):
vm_state=vm_states.SHELVED_OFFLOADED,
system_metadata=fake_sys_meta)
def test_delete_shelved_exception(self):
@ddt.data(True, False)
def test_delete_shelved_exception(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE_EXCEPTION}
self._test_delete('delete',
vm_state=vm_states.SHELVED,
@ -1269,7 +1278,9 @@ class _ComputeAPIUnitTestMixIn(object):
def test_delete_soft_in_resized(self):
self._test_delete('soft_delete', vm_state=vm_states.RESIZED)
def test_delete_soft(self):
@ddt.data(True, False)
def test_delete_soft(self, enable_consoleauth):
self.flags(enable_consoleauth=enable_consoleauth, group='workarounds')
self._test_delete('soft_delete')
def test_delete_forced(self):