Replace ApiError with new exceptions
* Convert ApiError to EC2APIError * Add new exceptions to replace ApiError where it didn't belong * Fixes bug 926250 Change-Id: Ia711440ee0313faf8ea8c87e2c0a2f5b39cc55a2
This commit is contained in:
@@ -1607,7 +1607,7 @@ class VsaDriveTypeCommands(object):
|
|||||||
"""Marks volume types as deleted"""
|
"""Marks volume types as deleted"""
|
||||||
try:
|
try:
|
||||||
volume_types.destroy(self.context, name)
|
volume_types.destroy(self.context, name)
|
||||||
except exception.ApiError:
|
except exception.InvalidVolumeType:
|
||||||
print "Valid volume type name is required"
|
print "Valid volume type name is required"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except exception.DBError, e:
|
except exception.DBError, e:
|
||||||
@@ -1786,7 +1786,7 @@ class InstanceTypeCommands(object):
|
|||||||
"""Marks instance types / flavors as deleted"""
|
"""Marks instance types / flavors as deleted"""
|
||||||
try:
|
try:
|
||||||
instance_types.destroy(name)
|
instance_types.destroy(name)
|
||||||
except exception.ApiError:
|
except exception.InstanceTypeNotFound:
|
||||||
print "Valid instance type name is required"
|
print "Valid instance type name is required"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except exception.DBError, e:
|
except exception.DBError, e:
|
||||||
|
@@ -80,7 +80,7 @@ class Error(Exception):
|
|||||||
super(Error, self).__init__(message)
|
super(Error, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
class ApiError(Error):
|
class EC2APIError(Error):
|
||||||
def __init__(self, message='Unknown', code=None):
|
def __init__(self, message='Unknown', code=None):
|
||||||
self.msg = message
|
self.msg = message
|
||||||
self.code = code
|
self.code = code
|
||||||
@@ -88,7 +88,7 @@ class ApiError(Error):
|
|||||||
outstr = '%s: %s' % (code, message)
|
outstr = '%s: %s' % (code, message)
|
||||||
else:
|
else:
|
||||||
outstr = '%s' % message
|
outstr = '%s' % message
|
||||||
super(ApiError, self).__init__(outstr)
|
super(EC2APIError, self).__init__(outstr)
|
||||||
|
|
||||||
|
|
||||||
class DBError(Error):
|
class DBError(Error):
|
||||||
@@ -223,6 +223,14 @@ class Invalid(NovaException):
|
|||||||
message = _("Unacceptable parameters.")
|
message = _("Unacceptable parameters.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSnapshot(Invalid):
|
||||||
|
message = _("Invalid snapshot") + ": %(reason)s"
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeUnattached(Invalid):
|
||||||
|
message = _("Volume %(volume_id)s is not attached to anything")
|
||||||
|
|
||||||
|
|
||||||
class InvalidKeypair(Invalid):
|
class InvalidKeypair(Invalid):
|
||||||
message = _("Keypair data is invalid")
|
message = _("Keypair data is invalid")
|
||||||
|
|
||||||
@@ -248,7 +256,11 @@ class InvalidInstanceType(Invalid):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidVolumeType(Invalid):
|
class InvalidVolumeType(Invalid):
|
||||||
message = _("Invalid volume type %(volume_type)s.")
|
message = _("Invalid volume type") + ": %(reason)s"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidVolume(Invalid):
|
||||||
|
message = _("Invalid volume") + ": %(reason)s"
|
||||||
|
|
||||||
|
|
||||||
class InvalidPortRange(Invalid):
|
class InvalidPortRange(Invalid):
|
||||||
@@ -930,9 +942,8 @@ class WillNotSchedule(NovaException):
|
|||||||
message = _("Host %(host)s is not up or doesn't exist.")
|
message = _("Host %(host)s is not up or doesn't exist.")
|
||||||
|
|
||||||
|
|
||||||
class QuotaError(ApiError):
|
class QuotaError(NovaException):
|
||||||
"""Quota Exceeded."""
|
message = _("Quota exceeded") + ": code=%(code)s"
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AggregateNotFound(NotFound):
|
class AggregateNotFound(NotFound):
|
||||||
@@ -962,3 +973,24 @@ class AggregateHostExists(Duplicate):
|
|||||||
|
|
||||||
class DuplicateSfVolumeNames(Duplicate):
|
class DuplicateSfVolumeNames(Duplicate):
|
||||||
message = _("Detected more than one volume with name %(vol_name)")
|
message = _("Detected more than one volume with name %(vol_name)")
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeTypeCreateFailed(NovaException):
|
||||||
|
message = _("Cannot create volume_type with "
|
||||||
|
"name %(name)s and specs %(extra_specs)s")
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceTypeCreateFailed(NovaException):
|
||||||
|
message = _("Unable to create instance type")
|
||||||
|
|
||||||
|
|
||||||
|
class SolidFireAPIException(NovaException):
|
||||||
|
message = _("Bad response from SolidFire API")
|
||||||
|
|
||||||
|
|
||||||
|
class SolidFireAPIStatusException(SolidFireAPIException):
|
||||||
|
message = _("Error in SolidFire API response: status=%(status)s")
|
||||||
|
|
||||||
|
|
||||||
|
class SolidFireAPIDataException(SolidFireAPIException):
|
||||||
|
message = _("Error in SolidFire API response: data=%(data)s")
|
||||||
|
@@ -13,14 +13,12 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from nova import context
|
|
||||||
from nova import db
|
from nova import db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import rpc
|
from nova import rpc
|
||||||
from nova.scheduler import vsa as vsa_sched
|
from nova.scheduler import vsa as vsa_sched
|
||||||
from nova import test
|
|
||||||
from nova.tests.scheduler import test_scheduler
|
from nova.tests.scheduler import test_scheduler
|
||||||
from nova import utils
|
from nova import utils
|
||||||
from nova.volume import volume_types
|
from nova.volume import volume_types
|
||||||
|
@@ -176,5 +176,5 @@ class SolidFireVolumeTestCase(test.TestCase):
|
|||||||
def test_get_cluster_info_fail(self):
|
def test_get_cluster_info_fail(self):
|
||||||
SFID._issue_api_request = self.fake_issue_api_request_fails
|
SFID._issue_api_request = self.fake_issue_api_request_fails
|
||||||
sfv = SFID()
|
sfv = SFID()
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.SolidFireAPIException,
|
||||||
sfv._get_cluster_info)
|
sfv._get_cluster_info)
|
||||||
|
@@ -2357,7 +2357,7 @@ class ComputeAPITestCase(BaseTestCase):
|
|||||||
address = '0.1.2.3'
|
address = '0.1.2.3'
|
||||||
|
|
||||||
self.compute.run_instance(self.context, instance_uuid)
|
self.compute.run_instance(self.context, instance_uuid)
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.FixedIpNotFoundForInstance,
|
||||||
self.compute_api.associate_floating_ip,
|
self.compute_api.associate_floating_ip,
|
||||||
self.context,
|
self.context,
|
||||||
instance,
|
instance,
|
||||||
@@ -2967,7 +2967,7 @@ class ComputeAPITestCase(BaseTestCase):
|
|||||||
self.compute_api.delete(self.context, instance)
|
self.compute_api.delete(self.context, instance)
|
||||||
|
|
||||||
def test_attach_volume_invalid(self):
|
def test_attach_volume_invalid(self):
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InvalidDevicePath,
|
||||||
self.compute_api.attach_volume,
|
self.compute_api.attach_volume,
|
||||||
self.context,
|
self.context,
|
||||||
None,
|
None,
|
||||||
|
@@ -85,7 +85,7 @@ class InstanceTypeTestCase(test.TestCase):
|
|||||||
'instance type was not created')
|
'instance type was not created')
|
||||||
|
|
||||||
instance_types.destroy(name)
|
instance_types.destroy(name)
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InstanceTypeNotFound,
|
||||||
instance_types.get_instance_type, inst_type_id)
|
instance_types.get_instance_type, inst_type_id)
|
||||||
|
|
||||||
# deleted instance should not be in list anymoer
|
# deleted instance should not be in list anymoer
|
||||||
@@ -133,7 +133,7 @@ class InstanceTypeTestCase(test.TestCase):
|
|||||||
'unknown_flavor')
|
'unknown_flavor')
|
||||||
|
|
||||||
def test_duplicate_names_fail(self):
|
def test_duplicate_names_fail(self):
|
||||||
"""Ensures that name duplicates raise ApiError"""
|
"""Ensures that name duplicates raise InstanceTypeCreateFailed"""
|
||||||
name = 'some_name'
|
name = 'some_name'
|
||||||
instance_types.create(name, 256, 1, 120, 200, 'flavor1')
|
instance_types.create(name, 256, 1, 120, 200, 'flavor1')
|
||||||
self.assertRaises(exception.InstanceTypeExists,
|
self.assertRaises(exception.InstanceTypeExists,
|
||||||
@@ -141,7 +141,7 @@ class InstanceTypeTestCase(test.TestCase):
|
|||||||
name, 256, 1, 120, 200, 'flavor2')
|
name, 256, 1, 120, 200, 'flavor2')
|
||||||
|
|
||||||
def test_duplicate_flavorids_fail(self):
|
def test_duplicate_flavorids_fail(self):
|
||||||
"""Ensures that flavorid duplicates raise ApiError"""
|
"""Ensures that flavorid duplicates raise InstanceTypeCreateFailed"""
|
||||||
flavorid = 'flavor1'
|
flavorid = 'flavor1'
|
||||||
instance_types.create('name one', 256, 1, 120, 200, flavorid)
|
instance_types.create('name one', 256, 1, 120, 200, flavorid)
|
||||||
self.assertRaises(exception.InstanceTypeExists,
|
self.assertRaises(exception.InstanceTypeExists,
|
||||||
@@ -156,7 +156,7 @@ class InstanceTypeTestCase(test.TestCase):
|
|||||||
def test_will_not_get_bad_default_instance_type(self):
|
def test_will_not_get_bad_default_instance_type(self):
|
||||||
"""ensures error raised on bad default instance type"""
|
"""ensures error raised on bad default instance type"""
|
||||||
self.flags(default_instance_type='unknown_flavor')
|
self.flags(default_instance_type='unknown_flavor')
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InstanceTypeNotFound,
|
||||||
instance_types.get_default_instance_type)
|
instance_types.get_default_instance_type)
|
||||||
|
|
||||||
def test_will_get_instance_type_by_id(self):
|
def test_will_get_instance_type_by_id(self):
|
||||||
@@ -167,12 +167,12 @@ class InstanceTypeTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_will_not_get_instance_type_by_unknown_id(self):
|
def test_will_not_get_instance_type_by_unknown_id(self):
|
||||||
"""Ensure get by name returns default flavor with no name"""
|
"""Ensure get by name returns default flavor with no name"""
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InstanceTypeNotFound,
|
||||||
instance_types.get_instance_type, 10000)
|
instance_types.get_instance_type, 10000)
|
||||||
|
|
||||||
def test_will_not_get_instance_type_with_bad_id(self):
|
def test_will_not_get_instance_type_with_bad_id(self):
|
||||||
"""Ensure get by name returns default flavor with bad name"""
|
"""Ensure get by name returns default flavor with bad name"""
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InstanceTypeNotFound,
|
||||||
instance_types.get_instance_type, 'asdf')
|
instance_types.get_instance_type, 'asdf')
|
||||||
|
|
||||||
def test_instance_type_get_by_None_name_returns_default(self):
|
def test_instance_type_get_by_None_name_returns_default(self):
|
||||||
@@ -183,7 +183,7 @@ class InstanceTypeTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_will_not_get_instance_type_with_bad_name(self):
|
def test_will_not_get_instance_type_with_bad_name(self):
|
||||||
"""Ensure get by name returns default flavor with bad name"""
|
"""Ensure get by name returns default flavor with bad name"""
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InstanceTypeNotFound,
|
||||||
instance_types.get_instance_type_by_name, 10000)
|
instance_types.get_instance_type_by_name, 10000)
|
||||||
|
|
||||||
def test_will_not_get_instance_by_unknown_flavor_id(self):
|
def test_will_not_get_instance_by_unknown_flavor_id(self):
|
||||||
|
@@ -249,7 +249,7 @@ class VolumeTestCase(test.TestCase):
|
|||||||
|
|
||||||
volume_api = nova.volume.api.API()
|
volume_api = nova.volume.api.API()
|
||||||
volume = volume_api.get(self.context, volume['id'])
|
volume = volume_api.get(self.context, volume['id'])
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InvalidVolume,
|
||||||
volume_api.create_snapshot,
|
volume_api.create_snapshot,
|
||||||
self.context, volume,
|
self.context, volume,
|
||||||
'fake_name', 'fake_description')
|
'fake_name', 'fake_description')
|
||||||
|
@@ -85,7 +85,7 @@ class VolumeTypeTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_non_existant_vol_type_shouldnt_delete(self):
|
def test_non_existant_vol_type_shouldnt_delete(self):
|
||||||
"""Ensures that volume type creation fails with invalid args"""
|
"""Ensures that volume type creation fails with invalid args"""
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.VolumeTypeNotFoundByName,
|
||||||
volume_types.destroy, self.ctxt, "sfsfsdfdfs")
|
volume_types.destroy, self.ctxt, "sfsfsdfdfs")
|
||||||
|
|
||||||
def test_repeated_vol_types_shouldnt_raise(self):
|
def test_repeated_vol_types_shouldnt_raise(self):
|
||||||
|
@@ -90,7 +90,7 @@ class VsaTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_vsa_create_wrong_image_name(self):
|
def test_vsa_create_wrong_image_name(self):
|
||||||
param = {'image_name': 'wrong_image_name'}
|
param = {'image_name': 'wrong_image_name'}
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.ImageNotFound,
|
||||||
self.vsa_api.create, self.context, **param)
|
self.vsa_api.create, self.context, **param)
|
||||||
|
|
||||||
def test_vsa_create_db_error(self):
|
def test_vsa_create_db_error(self):
|
||||||
@@ -100,19 +100,19 @@ class VsaTestCase(test.TestCase):
|
|||||||
raise exception.Error
|
raise exception.Error
|
||||||
|
|
||||||
self.stubs.Set(nova.db, 'vsa_create', fake_vsa_create)
|
self.stubs.Set(nova.db, 'vsa_create', fake_vsa_create)
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.Error,
|
||||||
self.vsa_api.create, self.context)
|
self.vsa_api.create, self.context)
|
||||||
|
|
||||||
def test_vsa_create_wrong_storage_params(self):
|
def test_vsa_create_wrong_storage_params(self):
|
||||||
vsa_list1 = self.vsa_api.get_all(self.context)
|
vsa_list1 = self.vsa_api.get_all(self.context)
|
||||||
param = {'storage': [{'stub': 1}]}
|
param = {'storage': [{'stub': 1}]}
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InvalidVolumeType,
|
||||||
self.vsa_api.create, self.context, **param)
|
self.vsa_api.create, self.context, **param)
|
||||||
vsa_list2 = self.vsa_api.get_all(self.context)
|
vsa_list2 = self.vsa_api.get_all(self.context)
|
||||||
self.assertEqual(len(vsa_list2), len(vsa_list1))
|
self.assertEqual(len(vsa_list2), len(vsa_list1))
|
||||||
|
|
||||||
param = {'storage': [{'drive_name': 'wrong name'}]}
|
param = {'storage': [{'drive_name': 'wrong name'}]}
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InvalidVolumeType,
|
||||||
self.vsa_api.create, self.context, **param)
|
self.vsa_api.create, self.context, **param)
|
||||||
|
|
||||||
def test_vsa_create_with_storage(self, multi_vol_creation=True):
|
def test_vsa_create_with_storage(self, multi_vol_creation=True):
|
||||||
|
@@ -107,14 +107,14 @@ class VsaVolumesTestCase(test.TestCase):
|
|||||||
'deleting')
|
'deleting')
|
||||||
|
|
||||||
def test_vsa_volume_delete_nonavail_volume(self):
|
def test_vsa_volume_delete_nonavail_volume(self):
|
||||||
""" Check volume deleton in different states. """
|
""" Check volume deletion in different states. """
|
||||||
volume_param = self._default_volume_param()
|
volume_param = self._default_volume_param()
|
||||||
volume_ref = self.volume_api.create(self.context, **volume_param)
|
volume_ref = self.volume_api.create(self.context, **volume_param)
|
||||||
|
|
||||||
self.volume_api.update(self.context,
|
self.volume_api.update(self.context,
|
||||||
volume_ref,
|
volume_ref,
|
||||||
{'status': 'in-use'})
|
{'status': 'in-use'})
|
||||||
self.assertRaises(exception.ApiError,
|
self.assertRaises(exception.InvalidVolume,
|
||||||
self.volume_api.delete,
|
self.volume_api.delete,
|
||||||
self.context, volume_ref)
|
self.context, volume_ref)
|
||||||
|
|
||||||
|
@@ -23,8 +23,6 @@ For assistance and guidelines pls contact
|
|||||||
Zadara Storage Inc & Openstack community
|
Zadara Storage Inc & Openstack community
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from nova import compute
|
from nova import compute
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
@@ -81,8 +79,8 @@ class API(base.Base):
|
|||||||
vol_type['extra_specs'].get('drive_type') is None or
|
vol_type['extra_specs'].get('drive_type') is None or
|
||||||
vol_type['extra_specs'].get('drive_size') is None):
|
vol_type['extra_specs'].get('drive_size') is None):
|
||||||
|
|
||||||
raise exception.ApiError(_("Invalid drive type %s")
|
msg = _("invalid drive data")
|
||||||
% vol_type['name'])
|
raise exception.InvalidVolumeType(reason=msg)
|
||||||
|
|
||||||
def _get_default_vsa_instance_type(self):
|
def _get_default_vsa_instance_type(self):
|
||||||
return instance_types.get_instance_type_by_name(
|
return instance_types.get_instance_type_by_name(
|
||||||
@@ -104,13 +102,14 @@ class API(base.Base):
|
|||||||
num_disks = node.get('num_drives', 1)
|
num_disks = node.get('num_drives', 1)
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
raise exception.ApiError(_("No drive_name param found in %s")
|
msg = _("drive_name not defined")
|
||||||
% node)
|
raise exception.InvalidVolumeType(reason=msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vol_type = volume_types.get_volume_type_by_name(context, name)
|
vol_type = volume_types.get_volume_type_by_name(context, name)
|
||||||
except exception.NotFound:
|
except exception.NotFound:
|
||||||
raise exception.ApiError(_("Invalid drive type name %s")
|
msg = _("invalid drive type name %s")
|
||||||
% name)
|
raise exception.InvalidVolumeType(reason=msg % name)
|
||||||
|
|
||||||
self._check_volume_type_correctness(vol_type)
|
self._check_volume_type_correctness(vol_type)
|
||||||
|
|
||||||
@@ -177,13 +176,10 @@ class API(base.Base):
|
|||||||
# check if image is ready before starting any work
|
# check if image is ready before starting any work
|
||||||
if image_name is None:
|
if image_name is None:
|
||||||
image_name = FLAGS.vc_image_name
|
image_name = FLAGS.vc_image_name
|
||||||
try:
|
|
||||||
image_service = self.compute_api.image_service
|
image_service = self.compute_api.image_service
|
||||||
vc_image = image_service.show_by_name(context, image_name)
|
vc_image = image_service.show_by_name(context, image_name)
|
||||||
vc_image_href = vc_image['id']
|
vc_image_href = vc_image['id']
|
||||||
except exception.ImageNotFound:
|
|
||||||
raise exception.ApiError(_("Failed to find configured image %s")
|
|
||||||
% image_name)
|
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'display_name': display_name,
|
'display_name': display_name,
|
||||||
@@ -198,10 +194,8 @@ class API(base.Base):
|
|||||||
LOG.info(_("Creating VSA: %s") % options)
|
LOG.info(_("Creating VSA: %s") % options)
|
||||||
|
|
||||||
# create DB entry for VSA instance
|
# create DB entry for VSA instance
|
||||||
try:
|
|
||||||
vsa_ref = self.db.vsa_create(context, options)
|
vsa_ref = self.db.vsa_create(context, options)
|
||||||
except exception.Error:
|
|
||||||
raise exception.ApiError(_(sys.exc_info()[1]))
|
|
||||||
vsa_id = vsa_ref['id']
|
vsa_id = vsa_ref['id']
|
||||||
vsa_name = vsa_ref['name']
|
vsa_name = vsa_ref['name']
|
||||||
|
|
||||||
@@ -209,10 +203,9 @@ class API(base.Base):
|
|||||||
try:
|
try:
|
||||||
volume_params = self._check_storage_parameters(context, vsa_name,
|
volume_params = self._check_storage_parameters(context, vsa_name,
|
||||||
storage, shared)
|
storage, shared)
|
||||||
except exception.ApiError:
|
except exception.InvalidVolumeType:
|
||||||
self.db.vsa_destroy(context, vsa_id)
|
self.db.vsa_destroy(context, vsa_id)
|
||||||
raise exception.ApiError(_("Error in storage parameters: %s")
|
raise
|
||||||
% storage)
|
|
||||||
|
|
||||||
# after creating DB entry, re-check and set some defaults
|
# after creating DB entry, re-check and set some defaults
|
||||||
updates = {}
|
updates = {}
|
||||||
@@ -358,7 +351,7 @@ class API(base.Base):
|
|||||||
LOG.info(_("VSA ID %(vsa_id)s: Deleting %(direction)s "
|
LOG.info(_("VSA ID %(vsa_id)s: Deleting %(direction)s "
|
||||||
"volume %(vol_name)s"), locals())
|
"volume %(vol_name)s"), locals())
|
||||||
self.volume_api.delete(context, volume)
|
self.volume_api.delete(context, volume)
|
||||||
except exception.ApiError:
|
except exception.InvalidVolume:
|
||||||
LOG.info(_("Unable to delete volume %s"), volume['name'])
|
LOG.info(_("Unable to delete volume %s"), volume['name'])
|
||||||
if force_delete:
|
if force_delete:
|
||||||
LOG.info(_("VSA ID %(vsa_id)s: Forced delete. "
|
LOG.info(_("VSA ID %(vsa_id)s: Forced delete. "
|
||||||
|
Reference in New Issue
Block a user