From 9af46f4688818b503bcb2954ec7bb8da3a654c84 Mon Sep 17 00:00:00 2001 From: Matthew Booth Date: Wed, 10 Mar 2021 23:09:30 +0000 Subject: [PATCH] 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 9ed17cec2d4ce0bd27e37c2877a413f5b302f4cd) --- neutron/objects/quota.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/neutron/objects/quota.py b/neutron/objects/quota.py index 668740b6661..d42d2a5694f 100644 --- a/neutron/objects/quota.py +++ b/neutron/objects/quota.py @@ -15,6 +15,7 @@ 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)