Stop allowing the deletion of an in-use flavor
Currently the API allows an operator to delete a flavor even when it is in use by a load balancer. This patch corrects this by blocking the deletion of a flavor that is currently in use by a load balancer. It also correctly handles load balancers in the "DELETED" provisioning_status. Change-Id: Ie6d4f74e36c2fb7cee4e0ff1e198602c5d8394cc Story: 2006782 Task: 37307
This commit is contained in:
parent
42bb73d364
commit
033c1e02ff
@ -47,6 +47,9 @@ class FlavorProfileController(base.BaseController):
|
|||||||
context = pecan.request.context.get('octavia_context')
|
context = pecan.request.context.get('octavia_context')
|
||||||
self._auth_validate_action(context, context.project_id,
|
self._auth_validate_action(context, context.project_id,
|
||||||
constants.RBAC_GET_ONE)
|
constants.RBAC_GET_ONE)
|
||||||
|
if id == constants.NIL_UUID:
|
||||||
|
raise exceptions.NotFound(resource='Flavor profile',
|
||||||
|
id=constants.NIL_UUID)
|
||||||
db_flavor_profile = self._get_db_flavor_profile(context.session, id)
|
db_flavor_profile = self._get_db_flavor_profile(context.session, id)
|
||||||
result = self._convert_db_to_type(db_flavor_profile,
|
result = self._convert_db_to_type(db_flavor_profile,
|
||||||
profile_types.FlavorProfileResponse)
|
profile_types.FlavorProfileResponse)
|
||||||
@ -140,6 +143,9 @@ class FlavorProfileController(base.BaseController):
|
|||||||
constants.RBAC_PUT)
|
constants.RBAC_PUT)
|
||||||
|
|
||||||
self._validate_update_fp(context, id, flavorprofile)
|
self._validate_update_fp(context, id, flavorprofile)
|
||||||
|
if id == constants.NIL_UUID:
|
||||||
|
raise exceptions.NotFound(resource='Flavor profile',
|
||||||
|
id=constants.NIL_UUID)
|
||||||
|
|
||||||
if not isinstance(flavorprofile.flavor_data, wtypes.UnsetType):
|
if not isinstance(flavorprofile.flavor_data, wtypes.UnsetType):
|
||||||
# Do a basic JSON validation on the metadata
|
# Do a basic JSON validation on the metadata
|
||||||
@ -189,12 +195,15 @@ class FlavorProfileController(base.BaseController):
|
|||||||
self._auth_validate_action(context, context.project_id,
|
self._auth_validate_action(context, context.project_id,
|
||||||
constants.RBAC_DELETE)
|
constants.RBAC_DELETE)
|
||||||
|
|
||||||
|
if flavor_profile_id == constants.NIL_UUID:
|
||||||
|
raise exceptions.NotFound(resource='Flavor profile',
|
||||||
|
id=constants.NIL_UUID)
|
||||||
|
|
||||||
# Don't allow it to be deleted if it is in use by a flavor
|
# Don't allow it to be deleted if it is in use by a flavor
|
||||||
if self.repositories.flavor.count(
|
if self.repositories.flavor.count(
|
||||||
context.session, flavor_profile_id=flavor_profile_id) > 0:
|
context.session, flavor_profile_id=flavor_profile_id) > 0:
|
||||||
raise exceptions.ObjectInUse(object='Flavor profile',
|
raise exceptions.ObjectInUse(object='Flavor profile',
|
||||||
id=flavor_profile_id)
|
id=flavor_profile_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.repositories.flavor_profile.delete(context.session,
|
self.repositories.flavor_profile.delete(context.session,
|
||||||
id=flavor_profile_id)
|
id=flavor_profile_id)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# 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 oslo_db import api as oslo_db_api
|
||||||
from oslo_db import exception as odb_exceptions
|
from oslo_db import exception as odb_exceptions
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
@ -44,7 +45,8 @@ class FlavorsController(base.BaseController):
|
|||||||
context = pecan.request.context.get('octavia_context')
|
context = pecan.request.context.get('octavia_context')
|
||||||
self._auth_validate_action(context, context.project_id,
|
self._auth_validate_action(context, context.project_id,
|
||||||
constants.RBAC_GET_ONE)
|
constants.RBAC_GET_ONE)
|
||||||
|
if id == constants.NIL_UUID:
|
||||||
|
raise exceptions.NotFound(resource='Flavor', id=constants.NIL_UUID)
|
||||||
db_flavor = self._get_db_flavor(context.session, id)
|
db_flavor = self._get_db_flavor(context.session, id)
|
||||||
result = self._convert_db_to_type(db_flavor,
|
result = self._convert_db_to_type(db_flavor,
|
||||||
flavor_types.FlavorResponse)
|
flavor_types.FlavorResponse)
|
||||||
@ -107,6 +109,8 @@ class FlavorsController(base.BaseController):
|
|||||||
context = pecan.request.context.get('octavia_context')
|
context = pecan.request.context.get('octavia_context')
|
||||||
self._auth_validate_action(context, context.project_id,
|
self._auth_validate_action(context, context.project_id,
|
||||||
constants.RBAC_PUT)
|
constants.RBAC_PUT)
|
||||||
|
if id == constants.NIL_UUID:
|
||||||
|
raise exceptions.NotFound(resource='Flavor', id=constants.NIL_UUID)
|
||||||
lock_session = db_api.get_session(autocommit=False)
|
lock_session = db_api.get_session(autocommit=False)
|
||||||
try:
|
try:
|
||||||
flavor_dict = flavor.to_dict(render_unsets=False)
|
flavor_dict = flavor.to_dict(render_unsets=False)
|
||||||
@ -126,6 +130,7 @@ class FlavorsController(base.BaseController):
|
|||||||
flavor_types.FlavorResponse)
|
flavor_types.FlavorResponse)
|
||||||
return flavor_types.FlavorRootResponse(flavor=result)
|
return flavor_types.FlavorRootResponse(flavor=result)
|
||||||
|
|
||||||
|
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
|
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
|
||||||
def delete(self, flavor_id):
|
def delete(self, flavor_id):
|
||||||
"""Deletes a Flavor"""
|
"""Deletes a Flavor"""
|
||||||
@ -133,10 +138,24 @@ class FlavorsController(base.BaseController):
|
|||||||
|
|
||||||
self._auth_validate_action(context, context.project_id,
|
self._auth_validate_action(context, context.project_id,
|
||||||
constants.RBAC_DELETE)
|
constants.RBAC_DELETE)
|
||||||
|
if flavor_id == constants.NIL_UUID:
|
||||||
|
raise exceptions.NotFound(resource='Flavor', id=constants.NIL_UUID)
|
||||||
|
serial_session = db_api.get_session(autocommit=False)
|
||||||
|
serial_session.connection(
|
||||||
|
execution_options={'isolation_level': 'SERIALIZABLE'})
|
||||||
try:
|
try:
|
||||||
self.repositories.flavor.delete(context.session, id=flavor_id)
|
self.repositories.flavor.delete(serial_session, id=flavor_id)
|
||||||
|
serial_session.commit()
|
||||||
# Handle when load balancers still reference this flavor
|
# Handle when load balancers still reference this flavor
|
||||||
except odb_exceptions.DBReferenceError:
|
except odb_exceptions.DBReferenceError:
|
||||||
|
serial_session.rollback()
|
||||||
raise exceptions.ObjectInUse(object='Flavor', id=flavor_id)
|
raise exceptions.ObjectInUse(object='Flavor', id=flavor_id)
|
||||||
except sa_exception.NoResultFound:
|
except sa_exception.NoResultFound:
|
||||||
|
serial_session.rollback()
|
||||||
raise exceptions.NotFound(resource='Flavor', id=flavor_id)
|
raise exceptions.NotFound(resource='Flavor', id=flavor_id)
|
||||||
|
except Exception as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.error('Unknown flavor delete exception: %s', str(e))
|
||||||
|
serial_session.rollback()
|
||||||
|
finally:
|
||||||
|
serial_session.close()
|
||||||
|
@ -716,3 +716,6 @@ SUPPORTED_VOLUME_DRIVERS = [VOLUME_NOOP_DRIVER,
|
|||||||
CINDER_STATUS_AVAILABLE = 'available'
|
CINDER_STATUS_AVAILABLE = 'available'
|
||||||
CINDER_STATUS_ERROR = 'error'
|
CINDER_STATUS_ERROR = 'error'
|
||||||
CINDER_ACTION_CREATE_VOLUME = 'create volume'
|
CINDER_ACTION_CREATE_VOLUME = 'create volume'
|
||||||
|
|
||||||
|
# The nil UUID (used in octavia for deleted references) - RFC 4122
|
||||||
|
NIL_UUID = '00000000-0000-0000-0000-000000000000'
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Add lb flavor ID constraint
|
||||||
|
|
||||||
|
Revision ID: e37941b010db
|
||||||
|
Revises: dcf88e59aae4
|
||||||
|
Create Date: 2019-10-31 10:09:37.869653
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import sql
|
||||||
|
|
||||||
|
from octavia.common import constants
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e37941b010db'
|
||||||
|
down_revision = 'dcf88e59aae4'
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
insert_table = sql.table(
|
||||||
|
u'flavor_profile',
|
||||||
|
sa.Column(u'id', sa.String(36), nullable=False),
|
||||||
|
sa.Column(u'name', sa.String(255), nullable=False),
|
||||||
|
sa.Column(u'provider_name', sa.String(255), nullable=False),
|
||||||
|
sa.Column(u'flavor_data', sa.String(4096), nullable=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
op.bulk_insert(
|
||||||
|
insert_table,
|
||||||
|
[
|
||||||
|
{'id': constants.NIL_UUID, 'name': 'DELETED-PLACEHOLDER',
|
||||||
|
'provider_name': 'DELETED', 'flavor_data': '{}'},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
insert_table = sql.table(
|
||||||
|
u'flavor',
|
||||||
|
sa.Column(u'id', sa.String(36), nullable=False),
|
||||||
|
sa.Column(u'name', sa.String(255), nullable=False),
|
||||||
|
sa.Column(u'description', sa.String(255), nullable=True),
|
||||||
|
sa.Column(u'enabled', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column(u'flavor_profile_id', sa.String(36), nullable=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
op.bulk_insert(
|
||||||
|
insert_table,
|
||||||
|
[
|
||||||
|
{'id': constants.NIL_UUID, 'name': 'DELETED-PLACEHOLDER',
|
||||||
|
'description': 'Placeholder for DELETED LBs with DELETED flavors',
|
||||||
|
'enabled': False, 'flavor_profile_id': constants.NIL_UUID}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure any existing load balancers with invalid flavor_id
|
||||||
|
# map to a valid flavor.
|
||||||
|
# Note: constant is not used here to not trigger security tool errors.
|
||||||
|
op.execute("UPDATE load_balancer LEFT JOIN flavor ON "
|
||||||
|
"load_balancer.flavor_id = flavor.id SET "
|
||||||
|
"load_balancer.flavor_id = "
|
||||||
|
"'00000000-0000-0000-0000-000000000000' WHERE "
|
||||||
|
"flavor.id IS NULL and load_balancer.flavor_id IS NOT NULL")
|
||||||
|
|
||||||
|
op.create_foreign_key('fk_loadbalancer_flavor_id', 'load_balancer',
|
||||||
|
'flavor', ['flavor_id'], ['id'])
|
@ -1809,7 +1809,39 @@ class QuotasRepository(BaseRepository):
|
|||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
|
|
||||||
class FlavorRepository(BaseRepository):
|
class _GetALLExceptDELETEDIdMixin(object):
|
||||||
|
|
||||||
|
def get_all(self, session, pagination_helper=None,
|
||||||
|
query_options=None, **filters):
|
||||||
|
|
||||||
|
"""Retrieves a list of entities from the database.
|
||||||
|
|
||||||
|
This filters the "DELETED" placeholder from the list.
|
||||||
|
|
||||||
|
:param session: A Sql Alchemy database session.
|
||||||
|
:param pagination_helper: Helper to apply pagination and sorting.
|
||||||
|
:param query_options: Optional query options to apply.
|
||||||
|
:param filters: Filters to decide which entities should be retrieved.
|
||||||
|
:returns: [octavia.common.data_model]
|
||||||
|
"""
|
||||||
|
query = session.query(self.model_class).filter_by(**filters)
|
||||||
|
if query_options:
|
||||||
|
query = query.options(query_options)
|
||||||
|
|
||||||
|
query = query.filter(self.model_class.id != consts.NIL_UUID)
|
||||||
|
|
||||||
|
if pagination_helper:
|
||||||
|
model_list, links = pagination_helper.apply(
|
||||||
|
query, self.model_class)
|
||||||
|
else:
|
||||||
|
links = None
|
||||||
|
model_list = query.all()
|
||||||
|
|
||||||
|
data_model_list = [model.to_data_model() for model in model_list]
|
||||||
|
return data_model_list, links
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorRepository(_GetALLExceptDELETEDIdMixin, BaseRepository):
|
||||||
model_class = models.Flavor
|
model_class = models.Flavor
|
||||||
|
|
||||||
def get_flavor_metadata_dict(self, session, flavor_id):
|
def get_flavor_metadata_dict(self, session, flavor_id):
|
||||||
@ -1831,8 +1863,26 @@ class FlavorRepository(BaseRepository):
|
|||||||
.filter(models.Flavor.flavor_profile_id ==
|
.filter(models.Flavor.flavor_profile_id ==
|
||||||
models.FlavorProfile.id).one()[0])
|
models.FlavorProfile.id).one()[0])
|
||||||
|
|
||||||
|
def delete(self, serial_session, **filters):
|
||||||
|
"""Sets DELETED LBs flavor_id to NIL_UUID, then removes the flavor
|
||||||
|
|
||||||
class FlavorProfileRepository(BaseRepository):
|
:param serial_session: A Sql Alchemy database transaction session.
|
||||||
|
:param filters: Filters to decide which entity should be deleted.
|
||||||
|
:returns: None
|
||||||
|
:raises: odb_exceptions.DBReferenceError
|
||||||
|
:raises: sqlalchemy.orm.exc.NoResultFound
|
||||||
|
"""
|
||||||
|
(serial_session.query(models.LoadBalancer).
|
||||||
|
filter(models.LoadBalancer.flavor_id == filters['id']).
|
||||||
|
filter(models.LoadBalancer.provisioning_status == consts.DELETED).
|
||||||
|
update({models.LoadBalancer.flavor_id: consts.NIL_UUID},
|
||||||
|
synchronize_session=False))
|
||||||
|
flavor = (serial_session.query(self.model_class).
|
||||||
|
filter_by(**filters).one())
|
||||||
|
serial_session.delete(flavor)
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorProfileRepository(_GetALLExceptDELETEDIdMixin, BaseRepository):
|
||||||
model_class = models.FlavorProfile
|
model_class = models.FlavorProfile
|
||||||
|
|
||||||
|
|
||||||
|
@ -171,6 +171,12 @@ class TestFlavorProfiles(base.BaseAPITest):
|
|||||||
self.assertEqual('name', response.get('name'))
|
self.assertEqual('name', response.get('name'))
|
||||||
self.assertEqual(fp.get('id'), response.get('id'))
|
self.assertEqual(fp.get('id'), response.get('id'))
|
||||||
|
|
||||||
|
def test_get_one_deleted_id(self):
|
||||||
|
response = self.get(self.FP_PATH.format(fp_id=constants.NIL_UUID),
|
||||||
|
status=404)
|
||||||
|
self.assertEqual('Flavor profile {} not found.'.format(
|
||||||
|
constants.NIL_UUID), response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_get_one_fields_filter(self):
|
def test_get_one_fields_filter(self):
|
||||||
fp = self.create_flavor_profile('name', 'noop_driver',
|
fp = self.create_flavor_profile('name', 'noop_driver',
|
||||||
'{"x": "y"}')
|
'{"x": "y"}')
|
||||||
@ -328,6 +334,14 @@ class TestFlavorProfiles(base.BaseAPITest):
|
|||||||
self.assertEqual('{"hello": "world"}',
|
self.assertEqual('{"hello": "world"}',
|
||||||
response.get(constants.FLAVOR_DATA))
|
response.get(constants.FLAVOR_DATA))
|
||||||
|
|
||||||
|
def test_update_deleted_id(self):
|
||||||
|
update_data = {'name': 'fake_profile'}
|
||||||
|
body = self._build_body(update_data)
|
||||||
|
response = self.put(self.FP_PATH.format(fp_id=constants.NIL_UUID),
|
||||||
|
body, status=404)
|
||||||
|
self.assertEqual('Flavor profile {} not found.'.format(
|
||||||
|
constants.NIL_UUID), response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_update_nothing(self):
|
def test_update_nothing(self):
|
||||||
fp = self.create_flavor_profile('test_profile', 'noop_driver',
|
fp = self.create_flavor_profile('test_profile', 'noop_driver',
|
||||||
'{"x": "y"}')
|
'{"x": "y"}')
|
||||||
@ -485,6 +499,18 @@ class TestFlavorProfiles(base.BaseAPITest):
|
|||||||
err_msg = "Flavor Profile %s not found." % fp.get('id')
|
err_msg = "Flavor Profile %s not found." % fp.get('id')
|
||||||
self.assertEqual(err_msg, response.json.get('faultstring'))
|
self.assertEqual(err_msg, response.json.get('faultstring'))
|
||||||
|
|
||||||
|
def test_delete_deleted_id(self):
|
||||||
|
response = self.delete(self.FP_PATH.format(fp_id=constants.NIL_UUID),
|
||||||
|
status=404)
|
||||||
|
self.assertEqual('Flavor profile {} not found.'.format(
|
||||||
|
constants.NIL_UUID), response.json.get('faultstring'))
|
||||||
|
|
||||||
|
def test_delete_nonexistent_id(self):
|
||||||
|
response = self.delete(self.FP_PATH.format(fp_id='bogus_id'),
|
||||||
|
status=404)
|
||||||
|
self.assertEqual('Flavor profile bogus_id not found.',
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_delete_authorized(self):
|
def test_delete_authorized(self):
|
||||||
fp = self.create_flavor_profile('test1', 'noop_driver',
|
fp = self.create_flavor_profile('test1', 'noop_driver',
|
||||||
'{"image": "ubuntu"}')
|
'{"image": "ubuntu"}')
|
||||||
|
@ -21,6 +21,7 @@ from oslo_config import fixture as oslo_fixture
|
|||||||
|
|
||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
import octavia.common.context
|
import octavia.common.context
|
||||||
|
from octavia.common import exceptions
|
||||||
from octavia.tests.functional.api.v2 import base
|
from octavia.tests.functional.api.v2 import base
|
||||||
|
|
||||||
|
|
||||||
@ -188,6 +189,12 @@ class TestFlavors(base.BaseAPITest):
|
|||||||
self.assertNotIn(u'description', response)
|
self.assertNotIn(u'description', response)
|
||||||
self.assertNotIn(u'enabled', response)
|
self.assertNotIn(u'enabled', response)
|
||||||
|
|
||||||
|
def test_get_one_deleted_id(self):
|
||||||
|
response = self.get(
|
||||||
|
self.FLAVOR_PATH.format(flavor_id=constants.NIL_UUID), status=404)
|
||||||
|
self.assertEqual('Flavor {} not found.'.format(constants.NIL_UUID),
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_get_authorized(self):
|
def test_get_authorized(self):
|
||||||
flavor = self.create_flavor('name', 'description', self.fp.get('id'),
|
flavor = self.create_flavor('name', 'description', self.fp.get('id'),
|
||||||
True)
|
True)
|
||||||
@ -357,6 +364,15 @@ class TestFlavors(base.BaseAPITest):
|
|||||||
updated_flavor.get('flavor_profile_id'))
|
updated_flavor.get('flavor_profile_id'))
|
||||||
self.assertFalse(updated_flavor.get('enabled'))
|
self.assertFalse(updated_flavor.get('enabled'))
|
||||||
|
|
||||||
|
def test_update_deleted_id(self):
|
||||||
|
update_json = {'name': 'fake_name'}
|
||||||
|
body = self._build_body(update_json)
|
||||||
|
response = self.put(
|
||||||
|
self.FLAVOR_PATH.format(flavor_id=constants.NIL_UUID), body,
|
||||||
|
status=404)
|
||||||
|
self.assertEqual('Flavor {} not found.'.format(constants.NIL_UUID),
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_update_none(self):
|
def test_update_none(self):
|
||||||
flavor_json = {'name': 'Fancy_Flavor',
|
flavor_json = {'name': 'Fancy_Flavor',
|
||||||
'description': 'A great flavor. Pick me!',
|
'description': 'A great flavor. Pick me!',
|
||||||
@ -478,6 +494,16 @@ class TestFlavors(base.BaseAPITest):
|
|||||||
updated_flavor.get('flavor_profile_id'))
|
updated_flavor.get('flavor_profile_id'))
|
||||||
self.assertTrue(updated_flavor.get('enabled'))
|
self.assertTrue(updated_flavor.get('enabled'))
|
||||||
|
|
||||||
|
@mock.patch('octavia.db.repositories.FlavorRepository.update')
|
||||||
|
def test_update_exception(self, mock_update):
|
||||||
|
mock_update.side_effect = [exceptions.OctaviaException()]
|
||||||
|
update_json = {'name': 'A_Flavor'}
|
||||||
|
body = self._build_body(update_json)
|
||||||
|
response = self.put(self.FLAVOR_PATH.format(flavor_id='bogus'), body,
|
||||||
|
status=500)
|
||||||
|
self.assertEqual('An unknown exception occurred.',
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
flavor = self.create_flavor('name1', 'description', self.fp.get('id'),
|
flavor = self.create_flavor('name1', 'description', self.fp.get('id'),
|
||||||
True)
|
True)
|
||||||
@ -488,6 +514,18 @@ class TestFlavors(base.BaseAPITest):
|
|||||||
err_msg = "Flavor %s not found." % flavor.get('id')
|
err_msg = "Flavor %s not found." % flavor.get('id')
|
||||||
self.assertEqual(err_msg, response.json.get('faultstring'))
|
self.assertEqual(err_msg, response.json.get('faultstring'))
|
||||||
|
|
||||||
|
def test_delete_nonexistent_id(self):
|
||||||
|
response = self.delete(
|
||||||
|
self.FLAVOR_PATH.format(flavor_id='bogus_id'), status=404)
|
||||||
|
self.assertEqual('Flavor bogus_id not found.',
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
|
||||||
|
def test_delete_deleted_id(self):
|
||||||
|
response = self.delete(
|
||||||
|
self.FLAVOR_PATH.format(flavor_id=constants.NIL_UUID), status=404)
|
||||||
|
self.assertEqual('Flavor {} not found.'.format(constants.NIL_UUID),
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
|
||||||
def test_delete_authorized(self):
|
def test_delete_authorized(self):
|
||||||
flavor = self.create_flavor('name1', 'description', self.fp.get('id'),
|
flavor = self.create_flavor('name1', 'description', self.fp.get('id'),
|
||||||
True)
|
True)
|
||||||
@ -556,3 +594,11 @@ class TestFlavors(base.BaseAPITest):
|
|||||||
response = self.get(self.FLAVOR_PATH.format(
|
response = self.get(self.FLAVOR_PATH.format(
|
||||||
flavor_id=flavor.get('id'))).json.get(self.root_tag)
|
flavor_id=flavor.get('id'))).json.get(self.root_tag)
|
||||||
self.assertEqual('name1', response.get('name'))
|
self.assertEqual('name1', response.get('name'))
|
||||||
|
|
||||||
|
@mock.patch('octavia.db.repositories.FlavorRepository.delete')
|
||||||
|
def test_delete_exception(self, mock_delete):
|
||||||
|
mock_delete.side_effect = [exceptions.OctaviaException()]
|
||||||
|
response = self.delete(self.FLAVOR_PATH.format(flavor_id='bogus'),
|
||||||
|
status=500)
|
||||||
|
self.assertEqual('An unknown exception occurred.',
|
||||||
|
response.json.get('faultstring'))
|
||||||
|
@ -96,6 +96,18 @@ class OctaviaDBTestBase(test_base.DbTestCase):
|
|||||||
models.L7PolicyAction)
|
models.L7PolicyAction)
|
||||||
self._seed_lookup_table(session, constants.SUPPORTED_CLIENT_AUTH_MODES,
|
self._seed_lookup_table(session, constants.SUPPORTED_CLIENT_AUTH_MODES,
|
||||||
models.ClientAuthenticationMode)
|
models.ClientAuthenticationMode)
|
||||||
|
# Add in the id='DELETED' placeholders
|
||||||
|
deleted_flavor_profile = models.FlavorProfile(
|
||||||
|
id=constants.NIL_UUID, name='DELETED-PLACEHOLDER',
|
||||||
|
provider_name=constants.DELETED, flavor_data='{}')
|
||||||
|
session.add(deleted_flavor_profile)
|
||||||
|
session.flush()
|
||||||
|
deleted_flavor = models.Flavor(
|
||||||
|
id=constants.NIL_UUID, flavor_profile_id=constants.NIL_UUID,
|
||||||
|
name='DELETED-PLACEHOLDER', enabled=False,
|
||||||
|
description='Placeholder for DELETED LBs with DELETED flavors')
|
||||||
|
session.add(deleted_flavor)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
def _seed_lookup_table(self, session, name_list, model_cls):
|
def _seed_lookup_table(self, session, name_list, model_cls):
|
||||||
for name in name_list:
|
for name in name_list:
|
||||||
|
@ -21,6 +21,7 @@ from oslo_config import cfg
|
|||||||
from oslo_config import fixture as oslo_fixture
|
from oslo_config import fixture as oslo_fixture
|
||||||
from oslo_db import exception as db_exception
|
from oslo_db import exception as db_exception
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
from sqlalchemy.orm import defer
|
||||||
from sqlalchemy.orm import exc as sa_exception
|
from sqlalchemy.orm import exc as sa_exception
|
||||||
|
|
||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
@ -4348,7 +4349,8 @@ class FlavorProfileRepositoryTest(BaseRepositoryTest):
|
|||||||
def test_get_all(self):
|
def test_get_all(self):
|
||||||
fp1 = self.create_flavor_profile(fp_id=self.FAKE_UUID_1)
|
fp1 = self.create_flavor_profile(fp_id=self.FAKE_UUID_1)
|
||||||
fp2 = self.create_flavor_profile(fp_id=self.FAKE_UUID_2)
|
fp2 = self.create_flavor_profile(fp_id=self.FAKE_UUID_2)
|
||||||
fp_list, _ = self.flavor_profile_repo.get_all(self.session)
|
fp_list, _ = self.flavor_profile_repo.get_all(
|
||||||
|
self.session, query_options=defer('name'))
|
||||||
self.assertIsInstance(fp_list, list)
|
self.assertIsInstance(fp_list, list)
|
||||||
self.assertEqual(2, len(fp_list))
|
self.assertEqual(2, len(fp_list))
|
||||||
self.assertEqual(fp1, fp_list[0])
|
self.assertEqual(fp1, fp_list[0])
|
||||||
@ -4395,7 +4397,8 @@ class FlavorRepositoryTest(BaseRepositoryTest):
|
|||||||
def test_get_all(self):
|
def test_get_all(self):
|
||||||
fl1 = self.create_flavor(flavor_id=self.FAKE_UUID_2, name='flavor1')
|
fl1 = self.create_flavor(flavor_id=self.FAKE_UUID_2, name='flavor1')
|
||||||
fl2 = self.create_flavor(flavor_id=self.FAKE_UUID_3, name='flavor2')
|
fl2 = self.create_flavor(flavor_id=self.FAKE_UUID_3, name='flavor2')
|
||||||
fl_list, _ = self.flavor_repo.get_all(self.session)
|
fl_list, _ = self.flavor_repo.get_all(self.session,
|
||||||
|
query_options=defer('enabled'))
|
||||||
self.assertIsInstance(fl_list, list)
|
self.assertIsInstance(fl_list, list)
|
||||||
self.assertEqual(2, len(fl_list))
|
self.assertEqual(2, len(fl_list))
|
||||||
self.assertEqual(fl1, fl_list[0])
|
self.assertEqual(fl1, fl_list[0])
|
||||||
|
Loading…
Reference in New Issue
Block a user