Fix invalid JSON generated by quota details

The value fetched by Quotasv2_detail.get_resources() is ultimately
returned by Reservation.get_total_reservations_map. The total_reserved
value is returned by a sqlalchemy sum(), and is of a type defined by
the database driver in use. PyMySQL returns a Decimal here, which
the api layer serialises to JSON as a string.

Note that if a resource has no reservations then its value will be
defaulted in DbQuotaDriver.get_detailed_tenant_quotas to the integer
value 0. This means that the value is an integer if zero, or a string
otherwise. This causes parsing difficulties for client libraries which
do strict type checking.

Testing note:
This method is already covered by the unit test
TestQuotaDbApi.test_get_reservations_for_resource, which also already
tests the return value and, implicitly, its type. However, it does not
currently fail because the unit tests run with SQLite rather than MySQL.
SQLite returns an int where MySQL returns a Decimal, which does not
trigger the bug.

TestQuotaDbApi.test_get_reservations_for_resource is sufficient to
demonstrate that the patch doesn't regress SQLite. We were not able to
think of a deterministic way to write a Tempest test for this, which
would cover MySQL. Ideally we would have the ability to run the DB test
suites under MySQL and PostreSQL in addition to SQLite using oslo.db's
OpportunisticDbFixture, but the scope of that is larger than this
change.

Closes-Bug: #1918565
Change-Id: Icda0d63f2f5ea72bde423296c76aa46646fa98a8
(cherry picked from commit 9ed17cec2d)
This commit is contained in:
Matthew Booth 2021-03-10 23:09:30 +00:00 committed by Slawek Kaplonski
parent e9d9b203cd
commit c3ffac8200
1 changed files with 4 additions and 1 deletions

View File

@ -16,6 +16,7 @@ from neutron_lib.objects import common_types
from oslo_versionedobjects import fields as obj_fields
import sqlalchemy as sa
from sqlalchemy import sql
from sqlalchemy import types as sqltypes
from neutron.db.quota import models
from neutron.objects import base
@ -90,7 +91,9 @@ class Reservation(base.NeutronDbObject):
resv_query = context.session.query(
models.ResourceDelta.resource,
models.Reservation.expiration,
sql.func.sum(models.ResourceDelta.amount)).join(
sql.func.cast(
sql.func.sum(models.ResourceDelta.amount),
sqltypes.Integer)).join(
models.Reservation)
if expired:
exp_expr = (models.Reservation.expiration < now)