placement project_id, user_id in PUT /allocations
This adds project_id and user_id required request parameters as part of a new microversion 1.8 of the placement API. Two new fields, for project and user ID, have been added to the AllocationList object, and the method AllocationList.create_all() has been changed to ensure that records are written to the consumers, projects, and users tables when project_id and user_id are not None. After an upgrade, new allocations will write consumer records and existing allocations will have corresponding consumer records written when they are updated as part of the resource tracker periodic task for updating available resources. Part of blueprint placement-project-user Co-Authored-By: Jay Pipes <jaypipes@gmail.com> Change-Id: I3c3b0cfdd33da87160255ead51a0d9ff73667655
This commit is contained in:
parent
df5b135053
commit
a909673682
|
@ -12,12 +12,14 @@
|
|||
"""Placement API handlers for setting and deleting allocations."""
|
||||
|
||||
import collections
|
||||
import copy
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import microversion
|
||||
from nova.api.openstack.placement import util
|
||||
from nova.api.openstack.placement import wsgi_wrapper
|
||||
from nova import exception
|
||||
|
@ -69,6 +71,15 @@ ALLOCATION_SCHEMA = {
|
|||
"additionalProperties": False
|
||||
}
|
||||
|
||||
ALLOCATION_SCHEMA_V1_8 = copy.deepcopy(ALLOCATION_SCHEMA)
|
||||
ALLOCATION_SCHEMA_V1_8['properties']['project_id'] = {'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 255}
|
||||
ALLOCATION_SCHEMA_V1_8['properties']['user_id'] = {'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 255}
|
||||
ALLOCATION_SCHEMA_V1_8['required'].extend(['project_id', 'user_id'])
|
||||
|
||||
|
||||
def _allocations_dict(allocations, key_fetcher, resource_provider=None):
|
||||
"""Turn allocations into a dict of resources keyed by key_fetcher."""
|
||||
|
@ -197,12 +208,10 @@ def list_for_resource_provider(req):
|
|||
return req.response
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@util.require_content('application/json')
|
||||
def set_allocations(req):
|
||||
def _set_allocations(req, schema):
|
||||
context = req.environ['placement.context']
|
||||
consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
|
||||
data = util.extract_json(req.body, ALLOCATION_SCHEMA)
|
||||
data = util.extract_json(req.body, schema)
|
||||
allocation_data = data['allocations']
|
||||
|
||||
# If the body includes an allocation for a resource provider
|
||||
|
@ -229,7 +238,12 @@ def set_allocations(req):
|
|||
used=resources[resource_class])
|
||||
allocation_objects.append(allocation)
|
||||
|
||||
allocations = objects.AllocationList(context, objects=allocation_objects)
|
||||
allocations = objects.AllocationList(
|
||||
context,
|
||||
objects=allocation_objects,
|
||||
project_id=data.get('project_id'),
|
||||
user_id=data.get('user_id'),
|
||||
)
|
||||
|
||||
try:
|
||||
allocations.create_all()
|
||||
|
@ -257,6 +271,20 @@ def set_allocations(req):
|
|||
return req.response
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
@microversion.version_handler('1.0', '1.7')
|
||||
@util.require_content('application/json')
|
||||
def set_allocations(req):
|
||||
return _set_allocations(req, ALLOCATION_SCHEMA)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify # noqa
|
||||
@microversion.version_handler('1.8')
|
||||
@util.require_content('application/json')
|
||||
def set_allocations(req):
|
||||
return _set_allocations(req, ALLOCATION_SCHEMA_V1_8)
|
||||
|
||||
|
||||
@wsgi_wrapper.PlacementWsgify
|
||||
def delete_allocations(req):
|
||||
context = req.environ['placement.context']
|
||||
|
|
|
@ -44,6 +44,8 @@ VERSIONS = [
|
|||
'1.6', # Adds /traits and /resource_providers{uuid}/traits resource
|
||||
# endpoints
|
||||
'1.7', # PUT /resource_classes/{name} is bodiless create or update
|
||||
'1.8', # Adds 'project_id' and 'user_id' required request parameters to
|
||||
# PUT /allocations
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ _RC_TBL = models.ResourceClass.__table__
|
|||
_AGG_TBL = models.PlacementAggregate.__table__
|
||||
_RP_AGG_TBL = models.ResourceProviderAggregate.__table__
|
||||
_RP_TRAIT_TBL = models.ResourceProviderTrait.__table__
|
||||
_PROJECT_TBL = models.Project.__table__
|
||||
_USER_TBL = models.User.__table__
|
||||
_CONSUMER_TBL = models.Consumer.__table__
|
||||
_RC_CACHE = None
|
||||
_TRAIT_LOCK = 'trait_sync'
|
||||
_TRAITS_SYNCED = False
|
||||
|
@ -1653,15 +1656,66 @@ def _check_capacity_exceeded(conn, allocs):
|
|||
return list(res_providers.values())
|
||||
|
||||
|
||||
def _ensure_lookup_table_entry(conn, tbl, external_id):
|
||||
"""Ensures the supplied external ID exists in the specified lookup table
|
||||
and if not, adds it. Returns the internal ID.
|
||||
|
||||
:param conn: DB connection object to use
|
||||
:param tbl: The lookup table
|
||||
:param external_id: The external project or user identifier
|
||||
:type external_id: string
|
||||
"""
|
||||
# Grab the project internal ID if it exists in the projects table
|
||||
sel = sa.select([tbl.c.id]).where(
|
||||
tbl.c.external_id == external_id
|
||||
)
|
||||
res = conn.execute(sel).fetchall()
|
||||
if not res:
|
||||
try:
|
||||
res = conn.execute(tbl.insert().values(external_id=external_id))
|
||||
return res.inserted_primary_key[0]
|
||||
except db_exc.DBDuplicateEntry:
|
||||
# Another thread added it just before us, so just read the
|
||||
# internal ID that that thread created...
|
||||
res = conn.execute(sel).fetchall()
|
||||
|
||||
return res[0][0]
|
||||
|
||||
|
||||
def _ensure_project(conn, external_id):
|
||||
"""Ensures the supplied external project ID exists in the projects lookup
|
||||
table and if not, adds it. Returns the internal project ID.
|
||||
|
||||
:param conn: DB connection object to use
|
||||
:param external_id: The external project identifier
|
||||
:type external_id: string
|
||||
"""
|
||||
return _ensure_lookup_table_entry(conn, _PROJECT_TBL, external_id)
|
||||
|
||||
|
||||
def _ensure_user(conn, external_id):
|
||||
"""Ensures the supplied external user ID exists in the users lookup table
|
||||
and if not, adds it. Returns the internal user ID.
|
||||
|
||||
:param conn: DB connection object to use
|
||||
:param external_id: The external user identifier
|
||||
:type external_id: string
|
||||
"""
|
||||
return _ensure_lookup_table_entry(conn, _USER_TBL, external_id)
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register
|
||||
class AllocationList(base.ObjectListBase, base.NovaObject):
|
||||
# Version 1.0: Initial Version
|
||||
# Version 1.1: Add create_all() and delete_all()
|
||||
# Version 1.2: Turn off remotable
|
||||
VERSION = '1.2'
|
||||
# Version 1.3: Add project_id and user_id fields
|
||||
VERSION = '1.3'
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('Allocation'),
|
||||
'project_id': fields.StringField(nullable=True),
|
||||
'user_id': fields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
@ -1686,9 +1740,40 @@ class AllocationList(base.ObjectListBase, base.NovaObject):
|
|||
models.Allocation.consumer_id == consumer_id)
|
||||
return query.all()
|
||||
|
||||
@staticmethod
|
||||
def _ensure_consumer_project_user(self, conn, consumer_id):
|
||||
"""Examines the project_id, user_id of the object along with the
|
||||
supplied consumer_id and ensures that there are records in the
|
||||
consumers, projects, and users table for these entities.
|
||||
|
||||
:param consumer_id: Comes from the Allocation object being processed
|
||||
"""
|
||||
if (self.obj_attr_is_set('project_id') and
|
||||
self.project_id is not None and
|
||||
self.obj_attr_is_set('user_id') and
|
||||
self.user_id is not None):
|
||||
# Grab the project internal ID if it exists in the projects table
|
||||
pid = _ensure_project(conn, self.project_id)
|
||||
# Grab the user internal ID if it exists in the users table
|
||||
uid = _ensure_user(conn, self.user_id)
|
||||
|
||||
# Add the consumer if it doesn't already exist
|
||||
sel_stmt = sa.select([_CONSUMER_TBL.c.uuid]).where(
|
||||
_CONSUMER_TBL.c.uuid == consumer_id)
|
||||
result = conn.execute(sel_stmt).fetchall()
|
||||
if not result:
|
||||
try:
|
||||
conn.execute(_CONSUMER_TBL.insert().values(
|
||||
uuid=consumer_id,
|
||||
project_id=pid,
|
||||
user_id=uid))
|
||||
except db_exc.DBDuplicateEntry:
|
||||
# We assume at this time that a consumer project/user can't
|
||||
# change, so if we get here, we raced and should just pass
|
||||
# if the consumer already exists.
|
||||
pass
|
||||
|
||||
@db_api.api_context_manager.writer
|
||||
def _set_allocations(context, allocs):
|
||||
def _set_allocations(self, context, allocs):
|
||||
"""Write a set of allocations.
|
||||
|
||||
We must check that there is capacity for each allocation.
|
||||
|
@ -1723,6 +1808,7 @@ class AllocationList(base.ObjectListBase, base.NovaObject):
|
|||
# First delete any existing allocations for that rp/consumer combo.
|
||||
_delete_current_allocs(conn, allocs)
|
||||
before_gens = _check_capacity_exceeded(conn, allocs)
|
||||
self._ensure_consumer_project_user(conn, allocs[0].consumer_id)
|
||||
# Now add the allocations that were passed in.
|
||||
for alloc in allocs:
|
||||
rp = alloc.resource_provider
|
||||
|
|
|
@ -839,13 +839,15 @@ class SchedulerReportClient(object):
|
|||
LOG.debug('Sending allocation for instance %s',
|
||||
my_allocations,
|
||||
instance=instance)
|
||||
res = self.put_allocations(rp_uuid, instance.uuid, my_allocations)
|
||||
res = self.put_allocations(rp_uuid, instance.uuid, my_allocations,
|
||||
instance.project_id, instance.user_id)
|
||||
if res:
|
||||
LOG.info(_LI('Submitted allocation for instance'),
|
||||
instance=instance)
|
||||
|
||||
@safe_connect
|
||||
def put_allocations(self, rp_uuid, consumer_uuid, alloc_data):
|
||||
def put_allocations(self, rp_uuid, consumer_uuid, alloc_data, project_id,
|
||||
user_id):
|
||||
"""Creates allocation records for the supplied instance UUID against
|
||||
the supplied resource provider.
|
||||
|
||||
|
@ -857,6 +859,8 @@ class SchedulerReportClient(object):
|
|||
:param consumer_uuid: The instance's UUID.
|
||||
:param alloc_data: Dict, keyed by resource class, of amounts to
|
||||
consume.
|
||||
:param project_id: The project_id associated with the allocations.
|
||||
:param user_id: The user_id associated with the allocations.
|
||||
:returns: True if the allocations were created, False otherwise.
|
||||
"""
|
||||
payload = {
|
||||
|
@ -868,9 +872,18 @@ class SchedulerReportClient(object):
|
|||
'resources': alloc_data,
|
||||
},
|
||||
],
|
||||
'project_id': project_id,
|
||||
'user_id': user_id,
|
||||
}
|
||||
url = '/allocations/%s' % consumer_uuid
|
||||
r = self.put(url, payload)
|
||||
r = self.put(url, payload, version='1.8')
|
||||
if r.status_code == 406:
|
||||
# microversion 1.8 not available so try the earlier way
|
||||
# TODO(melwitt): Remove this when we can be sure all placement
|
||||
# servers support version 1.8.
|
||||
payload.pop('project_id')
|
||||
payload.pop('user_id')
|
||||
r = self.put(url, payload)
|
||||
if r.status_code != 204:
|
||||
LOG.warning(
|
||||
'Unable to submit allocation for instance '
|
||||
|
|
|
@ -1426,7 +1426,7 @@ class PlacementFixture(fixtures.Fixture):
|
|||
headers={'x-auth-token': self.token},
|
||||
raise_exc=False)
|
||||
|
||||
def _fake_put(self, *args):
|
||||
def _fake_put(self, *args, **kwargs):
|
||||
(url, data) = args[1:]
|
||||
# NOTE(sdague): using json= instead of data= sets the
|
||||
# media type to application/json for us. Placement API is
|
||||
|
|
|
@ -77,6 +77,8 @@ class APIFixture(fixture.GabbiFixture):
|
|||
os.environ['RP_UUID'] = uuidutils.generate_uuid()
|
||||
os.environ['RP_NAME'] = uuidutils.generate_uuid()
|
||||
os.environ['CUSTOM_RES_CLASS'] = 'CUSTOM_IRON_NFV'
|
||||
os.environ['PROJECT_ID'] = uuidutils.generate_uuid()
|
||||
os.environ['USER_ID'] = uuidutils.generate_uuid()
|
||||
|
||||
def stop_fixture(self):
|
||||
self.api_db_fixture.cleanup()
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
fixtures:
|
||||
- APIFixture
|
||||
|
||||
defaults:
|
||||
request_headers:
|
||||
x-auth-token: admin
|
||||
accept: application/json
|
||||
OpenStack-API-Version: placement 1.8
|
||||
|
||||
tests:
|
||||
|
||||
- name: put an allocation no project_id or user_id
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
status: 400
|
||||
response_strings:
|
||||
- Failed validating 'required' in schema
|
||||
|
||||
- name: put an allocation no project_id
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
user_id: $ENVIRON['USER_ID']
|
||||
status: 400
|
||||
response_strings:
|
||||
- Failed validating 'required' in schema
|
||||
|
||||
- name: put an allocation no user_id
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
project_id: $ENVIRON['PROJECT_ID']
|
||||
status: 400
|
||||
response_strings:
|
||||
- Failed validating 'required' in schema
|
||||
|
||||
- name: put an allocation project_id less than min length
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
project_id: ""
|
||||
user_id: $ENVIRON['USER_ID']
|
||||
status: 400
|
||||
response_strings:
|
||||
- "Failed validating 'minLength'"
|
||||
|
||||
- name: put an allocation user_id less than min length
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
project_id: $ENVIRON['PROJECT_ID']
|
||||
user_id: ""
|
||||
status: 400
|
||||
response_strings:
|
||||
- "Failed validating 'minLength'"
|
||||
|
||||
- name: put an allocation project_id exceeds max length
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
project_id: 78725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b1
|
||||
user_id: $ENVIRON['USER_ID']
|
||||
status: 400
|
||||
response_strings:
|
||||
- "Failed validating 'maxLength'"
|
||||
|
||||
- name: put an allocation user_id exceeds max length
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
project_id: $ENVIRON['PROJECT_ID']
|
||||
user_id: 78725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b178725f09-5c01-4c9e-97a5-98d75e1e32b1
|
||||
status: 400
|
||||
response_strings:
|
||||
- "Failed validating 'maxLength'"
|
||||
|
||||
- name: create the resource provider
|
||||
POST: /resource_providers
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
name: $ENVIRON['RP_NAME']
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
status: 201
|
||||
|
||||
- name: post some inventory
|
||||
POST: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
resource_class: DISK_GB
|
||||
total: 2048
|
||||
min_unit: 10
|
||||
max_unit: 1024
|
||||
status: 201
|
||||
|
||||
- name: put an allocation
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
project_id: $ENVIRON['PROJECT_ID']
|
||||
user_id: $ENVIRON['USER_ID']
|
||||
status: 204
|
|
@ -39,13 +39,13 @@ tests:
|
|||
response_json_paths:
|
||||
$.errors[0].title: Not Acceptable
|
||||
|
||||
- name: latest microversion is 1.7
|
||||
- name: latest microversion is 1.8
|
||||
GET: /
|
||||
request_headers:
|
||||
openstack-api-version: placement latest
|
||||
response_headers:
|
||||
vary: /OpenStack-API-Version/
|
||||
openstack-api-version: placement 1.7
|
||||
openstack-api-version: placement 1.8
|
||||
|
||||
- name: other accept header bad version
|
||||
GET: /
|
||||
|
|
|
@ -76,6 +76,8 @@ class SchedulerReportClientTests(test.TestCase):
|
|||
self.instance_uuid = uuids.inst
|
||||
self.instance = objects.Instance(
|
||||
uuid=self.instance_uuid,
|
||||
project_id = uuids.project,
|
||||
user_id = uuids.user,
|
||||
flavor=objects.Flavor(root_gb=10,
|
||||
swap=1,
|
||||
ephemeral_gb=100,
|
||||
|
|
|
@ -123,6 +123,7 @@ class IronicResourceTrackerTest(test.TestCase):
|
|||
task_state=task_states.SPAWNING,
|
||||
power_state=power_state.RUNNING,
|
||||
project_id='project',
|
||||
user_id=uuids.user,
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -1213,6 +1213,51 @@ class TestAllocationListCreateDelete(ResourceProviderBaseCase):
|
|||
self._check_create_allocations(inventory_kwargs,
|
||||
bad_used, good_used)
|
||||
|
||||
def test_create_all_with_project_user(self):
|
||||
consumer_uuid = uuidsentinel.consumer
|
||||
rp_class = fields.ResourceClass.DISK_GB
|
||||
rp = self._make_rp_and_inventory(resource_class=rp_class,
|
||||
max_unit=500)
|
||||
allocation1 = objects.Allocation(resource_provider=rp,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=rp_class,
|
||||
used=100)
|
||||
allocation2 = objects.Allocation(resource_provider=rp,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=rp_class,
|
||||
used=200)
|
||||
allocation_list = objects.AllocationList(
|
||||
self.context,
|
||||
objects=[allocation1, allocation2],
|
||||
project_id=self.context.project_id,
|
||||
user_id=self.context.user_id,
|
||||
)
|
||||
allocation_list.create_all()
|
||||
|
||||
# Verify that we have records in the consumers, projects, and users
|
||||
# table for the information used in the above allocation creation
|
||||
with self.api_db.get_engine().connect() as conn:
|
||||
tbl = rp_obj._PROJECT_TBL
|
||||
sel = sa.select([tbl.c.id]).where(
|
||||
tbl.c.external_id == self.context.project_id,
|
||||
)
|
||||
res = conn.execute(sel).fetchall()
|
||||
self.assertEqual(1, len(res), "project lookup not created.")
|
||||
|
||||
tbl = rp_obj._USER_TBL
|
||||
sel = sa.select([tbl.c.id]).where(
|
||||
tbl.c.external_id == self.context.user_id,
|
||||
)
|
||||
res = conn.execute(sel).fetchall()
|
||||
self.assertEqual(1, len(res), "user lookup not created.")
|
||||
|
||||
tbl = rp_obj._CONSUMER_TBL
|
||||
sel = sa.select([tbl.c.id]).where(
|
||||
tbl.c.uuid == consumer_uuid,
|
||||
)
|
||||
res = conn.execute(sel).fetchall()
|
||||
self.assertEqual(1, len(res), "consumer lookup not created.")
|
||||
|
||||
|
||||
class UsageListTestCase(ResourceProviderBaseCase):
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ class TestMicroversionIntersection(test.NoDBTestCase):
|
|||
# if you add two different versions of method 'foobar' the
|
||||
# number only goes up by one if no other version foobar yet
|
||||
# exists. This operates as a simple sanity check.
|
||||
TOTAL_VERSIONED_METHODS = 12
|
||||
TOTAL_VERSIONED_METHODS = 13
|
||||
|
||||
def test_methods_versioned(self):
|
||||
methods_data = microversion.VERSIONED_METHODS
|
||||
|
|
|
@ -1063,7 +1063,7 @@ object_data = {
|
|||
'Aggregate': '1.3-f315cb68906307ca2d1cca84d4753585',
|
||||
'AggregateList': '1.2-fb6e19f3c3a3186b04eceb98b5dadbfa',
|
||||
'Allocation': '1.2-54f99dfa9651922219c205a7fba69e2f',
|
||||
'AllocationList': '1.2-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
||||
'AllocationList': '1.3-453a548b961f59804cccd05ae29ba4f7',
|
||||
'BandwidthUsage': '1.2-c6e4c779c7f40f2407e3d70022e3cd1c',
|
||||
'BandwidthUsageList': '1.2-5fe7475ada6fe62413cbfcc06ec70746',
|
||||
'BlockDeviceMapping': '1.18-ad87cece6f84c65f5ec21615755bc6d3',
|
||||
|
|
|
@ -187,9 +187,34 @@ class TestPutAllocations(SchedulerReportClientTestCase):
|
|||
consumer_uuid = mock.sentinel.consumer
|
||||
data = {"MEMORY_MB": 1024}
|
||||
expected_url = "/allocations/%s" % consumer_uuid
|
||||
resp = self.client.put_allocations(rp_uuid, consumer_uuid, data)
|
||||
resp = self.client.put_allocations(rp_uuid, consumer_uuid, data,
|
||||
mock.sentinel.project_id,
|
||||
mock.sentinel.user_id)
|
||||
self.assertTrue(resp)
|
||||
mock_put.assert_called_once_with(expected_url, mock.ANY)
|
||||
mock_put.assert_called_once_with(expected_url, mock.ANY, version='1.8')
|
||||
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.put')
|
||||
def test_put_allocations_fail_fallback_succeeds(self, mock_put):
|
||||
not_acceptable = mock.Mock()
|
||||
not_acceptable.status_code = 406
|
||||
not_acceptable.text = 'microversion not supported'
|
||||
ok_request = mock.Mock()
|
||||
ok_request.status_code = 204
|
||||
ok_request.text = 'cool'
|
||||
mock_put.side_effect = [not_acceptable, ok_request]
|
||||
rp_uuid = mock.sentinel.rp
|
||||
consumer_uuid = mock.sentinel.consumer
|
||||
data = {"MEMORY_MB": 1024}
|
||||
expected_url = "/allocations/%s" % consumer_uuid
|
||||
resp = self.client.put_allocations(rp_uuid, consumer_uuid, data,
|
||||
mock.sentinel.project_id,
|
||||
mock.sentinel.user_id)
|
||||
self.assertTrue(resp)
|
||||
# Should fall back to earlier way if 1.8 fails.
|
||||
call1 = mock.call(expected_url, mock.ANY, version='1.8')
|
||||
call2 = mock.call(expected_url, mock.ANY)
|
||||
self.assertEqual(2, mock_put.call_count)
|
||||
mock_put.assert_has_calls([call1, call2])
|
||||
|
||||
@mock.patch.object(report.LOG, 'warning')
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.put')
|
||||
|
@ -200,9 +225,11 @@ class TestPutAllocations(SchedulerReportClientTestCase):
|
|||
consumer_uuid = mock.sentinel.consumer
|
||||
data = {"MEMORY_MB": 1024}
|
||||
expected_url = "/allocations/%s" % consumer_uuid
|
||||
resp = self.client.put_allocations(rp_uuid, consumer_uuid, data)
|
||||
resp = self.client.put_allocations(rp_uuid, consumer_uuid, data,
|
||||
mock.sentinel.project_id,
|
||||
mock.sentinel.user_id)
|
||||
self.assertFalse(resp)
|
||||
mock_put.assert_called_once_with(expected_url, mock.ANY)
|
||||
mock_put.assert_called_once_with(expected_url, mock.ANY, version='1.8')
|
||||
log_msg = mock_warn.call_args[0][0]
|
||||
self.assertIn("Unable to submit allocation for instance", log_msg)
|
||||
|
||||
|
@ -1427,17 +1454,20 @@ class TestAllocations(SchedulerReportClientTestCase):
|
|||
def test_update_instance_allocation_new(self, mock_a, mock_get,
|
||||
mock_put):
|
||||
cn = objects.ComputeNode(uuid=uuids.cn)
|
||||
inst = objects.Instance(uuid=uuids.inst)
|
||||
inst = objects.Instance(uuid=uuids.inst, project_id=uuids.project,
|
||||
user_id=uuids.user)
|
||||
mock_get.return_value.json.return_value = {'allocations': {}}
|
||||
expected = {
|
||||
'allocations': [
|
||||
{'resource_provider': {'uuid': cn.uuid},
|
||||
'resources': mock_a.return_value}]
|
||||
'resources': mock_a.return_value}],
|
||||
'project_id': inst.project_id,
|
||||
'user_id': inst.user_id,
|
||||
}
|
||||
self.client.update_instance_allocation(cn, inst, 1)
|
||||
mock_put.assert_called_once_with(
|
||||
'/allocations/%s' % inst.uuid,
|
||||
expected)
|
||||
expected, version='1.8')
|
||||
self.assertTrue(mock_get.called)
|
||||
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
||||
|
@ -1478,7 +1508,8 @@ class TestAllocations(SchedulerReportClientTestCase):
|
|||
def test_update_instance_allocation_new_failed(self, mock_warn, mock_a,
|
||||
mock_put, mock_get):
|
||||
cn = objects.ComputeNode(uuid=uuids.cn)
|
||||
inst = objects.Instance(uuid=uuids.inst)
|
||||
inst = objects.Instance(uuid=uuids.inst, project_id=uuids.project,
|
||||
user_id=uuids.user)
|
||||
try:
|
||||
mock_put.return_value.__nonzero__.return_value = False
|
||||
except AttributeError:
|
||||
|
|
Loading…
Reference in New Issue