Merge "correctly join the usage to inventory for capacity accounting"
This commit is contained in:
commit
2bd800ce25
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import func
|
||||
@ -19,6 +20,7 @@ from sqlalchemy import sql
|
||||
from nova.db.sqlalchemy import api as db_api
|
||||
from nova.db.sqlalchemy import api_models as models
|
||||
from nova import exception
|
||||
from nova.i18n import _LW
|
||||
from nova import objects
|
||||
from nova.objects import base
|
||||
from nova.objects import fields
|
||||
@ -27,6 +29,8 @@ _ALLOC_TBL = models.Allocation.__table__
|
||||
_INV_TBL = models.Inventory.__table__
|
||||
_RP_TBL = models.ResourceProvider.__table__
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_current_inventory_resources(conn, rp):
|
||||
"""Returns a set() containing the resource class IDs for all resources
|
||||
@ -685,8 +689,10 @@ def _check_capacity_exceeded(conn, allocs):
|
||||
sql.and_(_RP_TBL.c.id == _INV_TBL.c.resource_provider_id,
|
||||
_INV_TBL.c.resource_class_id.in_(res_classes)))
|
||||
primary_join = sql.outerjoin(inv_join, usage,
|
||||
_INV_TBL.c.resource_provider_id == usage.c.resource_provider_id)
|
||||
|
||||
sql.and_(
|
||||
_INV_TBL.c.resource_provider_id == usage.c.resource_provider_id,
|
||||
_INV_TBL.c.resource_class_id == usage.c.resource_class_id)
|
||||
)
|
||||
cols_in_output = [
|
||||
_RP_TBL.c.id.label('resource_provider_id'),
|
||||
_RP_TBL.c.uuid,
|
||||
@ -707,7 +713,10 @@ def _check_capacity_exceeded(conn, allocs):
|
||||
usage_map = {}
|
||||
provs_with_inv = set()
|
||||
for record in records:
|
||||
usage_map[(record['uuid'], record['resource_class_id'])] = record
|
||||
map_key = (record['uuid'], record['resource_class_id'])
|
||||
if map_key in usage_map:
|
||||
raise KeyError("%s already in usage_map, bad query" % str(map_key))
|
||||
usage_map[map_key] = record
|
||||
provs_with_inv.add(record["uuid"])
|
||||
# Ensure that all providers have existing inventory
|
||||
missing_provs = provider_uuids - provs_with_inv
|
||||
@ -727,6 +736,14 @@ def _check_capacity_exceeded(conn, allocs):
|
||||
used = usage['used'] or 0
|
||||
capacity = (usage['total'] - usage['reserved']) * allocation_ratio
|
||||
if capacity < (used + amount_needed):
|
||||
LOG.warning(
|
||||
_LW("Over capacity for %(rc)s on resource provider %(rp)s. "
|
||||
"Needed: %(needed)s, Used: %(used)s, Capacity: %(cap)s"),
|
||||
{'rc': fields.ResourceClass.from_index(res_class),
|
||||
'rp': rp_uuid,
|
||||
'needed': amount_needed,
|
||||
'used': used,
|
||||
'cap': capacity})
|
||||
raise exception.InvalidAllocationCapacityExceeded(
|
||||
resource_class=fields.ResourceClass.from_index(res_class),
|
||||
resource_provider=rp_uuid)
|
||||
|
@ -574,6 +574,71 @@ class TestAllocation(ResourceProviderBaseCase):
|
||||
|
||||
class TestAllocationListCreateDelete(ResourceProviderBaseCase):
|
||||
|
||||
def test_allocation_checking(self):
|
||||
"""Test that allocation check logic works with 2 resource classes on
|
||||
one provider.
|
||||
|
||||
If this fails, we get a KeyError at create_all()
|
||||
"""
|
||||
|
||||
consumer_uuid = uuidsentinel.consumer
|
||||
consumer_uuid2 = uuidsentinel.consumer2
|
||||
|
||||
# Create one resource provider with 2 classes
|
||||
rp1_name = uuidsentinel.rp1_name
|
||||
rp1_uuid = uuidsentinel.rp1_uuid
|
||||
rp1_class = fields.ResourceClass.DISK_GB
|
||||
rp1_used = 6
|
||||
|
||||
rp2_class = fields.ResourceClass.IPV4_ADDRESS
|
||||
rp2_used = 2
|
||||
|
||||
rp1 = objects.ResourceProvider(
|
||||
self.context, name=rp1_name, uuid=rp1_uuid)
|
||||
rp1.create()
|
||||
|
||||
inv = objects.Inventory(resource_provider=rp1,
|
||||
resource_class=rp1_class,
|
||||
total=1024)
|
||||
inv.obj_set_defaults()
|
||||
|
||||
inv2 = objects.Inventory(resource_provider=rp1,
|
||||
resource_class=rp2_class,
|
||||
total=255, reserved=2)
|
||||
inv2.obj_set_defaults()
|
||||
inv_list = objects.InventoryList(objects=[inv, inv2])
|
||||
rp1.set_inventory(inv_list)
|
||||
|
||||
# create the allocations for a first consumer
|
||||
allocation_1 = objects.Allocation(resource_provider=rp1,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=rp1_class,
|
||||
used=rp1_used)
|
||||
allocation_2 = objects.Allocation(resource_provider=rp1,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=rp2_class,
|
||||
used=rp2_used)
|
||||
allocation_list = objects.AllocationList(
|
||||
self.context, objects=[allocation_1, allocation_2])
|
||||
allocation_list.create_all()
|
||||
|
||||
# create the allocations for a second consumer, until we have
|
||||
# allocations for more than one consumer in the db, then we
|
||||
# won't actually be doing real allocation math, which triggers
|
||||
# the sql monster.
|
||||
allocation_1 = objects.Allocation(resource_provider=rp1,
|
||||
consumer_id=consumer_uuid2,
|
||||
resource_class=rp1_class,
|
||||
used=rp1_used)
|
||||
allocation_2 = objects.Allocation(resource_provider=rp1,
|
||||
consumer_id=consumer_uuid2,
|
||||
resource_class=rp2_class,
|
||||
used=rp2_used)
|
||||
allocation_list = objects.AllocationList(
|
||||
self.context, objects=[allocation_1, allocation_2])
|
||||
# If we are joining wrong, this will be a KeyError
|
||||
allocation_list.create_all()
|
||||
|
||||
def test_allocation_list_create(self):
|
||||
consumer_uuid = uuidsentinel.consumer
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user