Use dict object for request_specs_dict in the _list_view
The request_specs_dict in the _list_view is initialized as a defaultdict object in order to return empty string as default. But the request_spec_dict is replaced with a normal dict object in the v2.96 microversion, then if server list and RequestSpec missmatch happens by any reason, the List Server API and the List Server Detail API hit 500 Internal server error because of key error. This commit updates the req_spec_dict to use normal dict object, then it returns sentinel object if there is no appropriate request_spec object. Closes-Bug: #2095364 Change-Id: If282b8709954f276cb5d48114437809d771a9958
This commit is contained in:
@@ -5721,6 +5721,7 @@ pinned_availability_zone:
|
||||
Also when default_schedule_zone config option set to specific AZ, in that
|
||||
case, instance would be pinned to that specific AZ, and instance will be
|
||||
scheduled on host belonging to pinned AZ.
|
||||
In case of no pinned availability zone, this value is set to `null`.
|
||||
in: body
|
||||
type: string
|
||||
min_version: 2.96
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
@@ -41,6 +39,7 @@ from nova import utils
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
AZ_NOT_IN_REQUEST_SPEC = object()
|
||||
SCHED_HINTS_NOT_IN_REQUEST_SPEC = object()
|
||||
|
||||
|
||||
@@ -225,16 +224,20 @@ class ViewBuilder(common.ViewBuilder):
|
||||
return unknown_only
|
||||
|
||||
def _get_pinned_az(self, context, instance, provided_az):
|
||||
pinned_az = ''
|
||||
if provided_az is not None:
|
||||
if provided_az is AZ_NOT_IN_REQUEST_SPEC:
|
||||
# Case the provided_az is pre fetched, but not specified
|
||||
pinned_az = None
|
||||
elif provided_az is not None:
|
||||
# Case the provided_az is pre fetched, and specified
|
||||
pinned_az = provided_az
|
||||
else:
|
||||
# Case the provided_az is not pre fethed.
|
||||
try:
|
||||
req_spec = objects.RequestSpec.get_by_instance_uuid(
|
||||
context, instance.uuid)
|
||||
pinned_az = req_spec.availability_zone
|
||||
except exception.RequestSpecNotFound:
|
||||
pinned_az = ''
|
||||
pinned_az = None
|
||||
return pinned_az
|
||||
|
||||
def _get_scheduler_hints(self, context, instance, provided_sched_hints):
|
||||
@@ -543,15 +546,16 @@ class ViewBuilder(common.ViewBuilder):
|
||||
:returns: Server data in dictionary format
|
||||
"""
|
||||
req_specs = None
|
||||
req_specs_dict = collections.defaultdict(str)
|
||||
req_specs_dict = {}
|
||||
sched_hints_dict = {}
|
||||
if api_version_request.is_supported(request, min_version='2.96'):
|
||||
context = request.environ['nova.context']
|
||||
instance_uuids = [s.uuid for s in servers]
|
||||
req_specs = objects.RequestSpec.get_by_instance_uuids(
|
||||
context, instance_uuids)
|
||||
req_specs_dict = {req.instance_uuid: req.availability_zone
|
||||
for req in req_specs}
|
||||
req_specs_dict.update({req.instance_uuid: req.availability_zone
|
||||
for req in req_specs
|
||||
if req.availability_zone is not None})
|
||||
if api_version_request.is_supported(request, min_version='2.100'):
|
||||
sched_hints_dict.update({
|
||||
req.instance_uuid: req.scheduler_hints
|
||||
@@ -565,7 +569,8 @@ class ViewBuilder(common.ViewBuilder):
|
||||
show_host_status=show_host_status,
|
||||
show_sec_grp=show_sec_grp, bdms=bdms,
|
||||
cell_down_support=cell_down_support,
|
||||
provided_az=req_specs_dict[server.uuid],
|
||||
provided_az=req_specs_dict.get(
|
||||
server.uuid, AZ_NOT_IN_REQUEST_SPEC),
|
||||
provided_sched_hints=sched_hints_dict.get(
|
||||
server.uuid, SCHED_HINTS_NOT_IN_REQUEST_SPEC)
|
||||
)["server"]
|
||||
|
||||
@@ -8466,8 +8466,74 @@ class ServersViewBuilderTestV296(_ServersViewBuilderTest):
|
||||
availability_zone=self.instance.availability_zone)]
|
||||
|
||||
req = self.req('/%s/servers' % self.project_id)
|
||||
self.assertRaises(KeyError, self.view_builder.index,
|
||||
req, self.instances, False)
|
||||
output = self.view_builder.index(req, self.instances, False)
|
||||
|
||||
self.assertEqual(2, len(output['servers']))
|
||||
|
||||
@mock.patch('nova.objects.RequestSpec.get_by_instance_uuids')
|
||||
def test_list_detail_view_with_missing_request_specs(self, m_rs):
|
||||
|
||||
self.instances = [
|
||||
self.instance,
|
||||
self.create_instance(2, uuids.fake1, 'fake-server'),
|
||||
self.create_instance(3, uuids.fake2, 'fake-server2')
|
||||
]
|
||||
# First instance's request spec has pinned availability zone
|
||||
# Second instance's request spec has no pinned availability zone
|
||||
m_rs.return_value = [
|
||||
objects.RequestSpec(
|
||||
instance_uuid=self.instance.uuid,
|
||||
availability_zone=self.instance.availability_zone),
|
||||
objects.RequestSpec(
|
||||
instance_uuid=self.instance.uuid,
|
||||
availability_zone=None)
|
||||
]
|
||||
|
||||
req = self.req('/%s/servers/detail' % self.project_id)
|
||||
output = self.view_builder.detail(req, self.instances, False)
|
||||
|
||||
self.assertEqual(3, len(output['servers']))
|
||||
# first instance has pinned az
|
||||
self.assertEqual('nova',
|
||||
output['servers'][0]['pinned_availability_zone'])
|
||||
# second or later has no pinned az
|
||||
for s in output['servers'][1:]:
|
||||
self.assertIsNone(s['pinned_availability_zone'])
|
||||
|
||||
@mock.patch('nova.objects.RequestSpec.get_by_instance_uuid')
|
||||
def test_show_view_with_missing_request_specs(self, m_rs):
|
||||
|
||||
self.instances = [
|
||||
self.instance,
|
||||
self.create_instance(2, uuids.fake1, 'fake-server'),
|
||||
self.create_instance(3, uuids.fake2, 'fake-server2')
|
||||
]
|
||||
# First instance's request spec has pinned availability zone
|
||||
# Second instance's request spec has no pinned availability zone
|
||||
m_rs.side_effect = [
|
||||
objects.RequestSpec(
|
||||
instance_uuid=self.instance.uuid,
|
||||
availability_zone=self.instance.availability_zone),
|
||||
objects.RequestSpec(
|
||||
instance_uuid=self.instance.uuid,
|
||||
availability_zone=None),
|
||||
exception.RequestSpecNotFound(instance_uuid='3')
|
||||
]
|
||||
|
||||
# Instance show with request spec and pinned az
|
||||
req = self.req('/%s/servers/1' % self.project_id)
|
||||
output = self.view_builder.show(req, self.instances[0])
|
||||
self.assertEqual('nova', output['server']['pinned_availability_zone'])
|
||||
|
||||
# Instance show with request spec and no pinned az
|
||||
req = self.req('/%s/servers/2' % self.project_id)
|
||||
output = self.view_builder.show(req, self.instances[1])
|
||||
self.assertIsNone(output['server']['pinned_availability_zone'])
|
||||
|
||||
# Instance show without request spec
|
||||
req = self.req('/%s/servers/3' % self.project_id)
|
||||
output = self.view_builder.show(req, self.instances[2])
|
||||
self.assertIsNone(output['server']['pinned_availability_zone'])
|
||||
|
||||
|
||||
class ServersViewBuilderTestV2100(_ServersViewBuilderTest):
|
||||
|
||||
15
releasenotes/notes/bug-2095364-ffbf67c0ae3f53b5.yaml
Normal file
15
releasenotes/notes/bug-2095364-ffbf67c0ae3f53b5.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
`Bug #2095364`_: Fixed the List Server API and the List Server Detail API
|
||||
500 Internal Server Error issue in v2.96 or later API microversion if
|
||||
one or more instance has no request spec object. One usecase was when cloud
|
||||
user tried to create instance which exceeded their quota, the request does
|
||||
not create instance request spec. Once the no request spec instance is
|
||||
created in cloud user project, the server list API and the list server
|
||||
details API return 500 Internal Server Error for the project until the
|
||||
cloud user deletes the no request spec object instance.
|
||||
After this fix, the v2.96 or later returns `null` at the
|
||||
`pinned_availability_zone` value if not specified.
|
||||
|
||||
.. _Bug #2095364: https://launchpad.net/bugs/2095364
|
||||
Reference in New Issue
Block a user