Usages per project and user (v1.8, v1.9)

Adds support to "openstack resource provider allocation set"
for microversion 1.8 where --project-id and --user-id are
required (and highly recommended).

Adds a new "openstack resource usage show" command
for microversion 1.9 and GET /usages.

Change-Id: I8ec19c8138611a06b41ec3ad949f45b8f6d7ec17
Partially-Implements: blueprint placement-osc-plugin-rocky
This commit is contained in:
Andrey Volkov 2017-10-23 13:16:18 +03:00 committed by Matt Riedemann
parent 06b5738a3a
commit fcc8081df7
8 changed files with 201 additions and 9 deletions

View File

@ -14,6 +14,8 @@ from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
from osc_placement import version
BASE_URL = '/allocations'
FIELDS = ('generation', 'resources')
@ -43,12 +45,18 @@ def parse_allocations(allocation_strings):
return allocations
class SetAllocation(command.Lister):
"""Replaces the set of resource allocation(s) for a given consumer
class SetAllocation(command.Lister, version.CheckerMixin):
"""Replaces the set of resource allocation(s) for a given consumer.
Note that this is a full replacement of the existing allocations. If you
want to retain the existing allocations and add a new resource class
allocation, you must specify all resource class allocations, old and new.
From ``--os-placement-api-version 1.8`` it is required to specify
``--project-id`` and ``--user-id`` to set allocations. It is highly
recommended to provide a ``--project-id`` and ``--user-id`` when setting
allocations for accounting and data consistency reasons.
"""
def get_parser(self, prog_name):
@ -68,7 +76,22 @@ class SetAllocation(command.Lister):
help='Create (or update) an allocation of a resource class. '
'Specify option multiple times to set multiple allocations.'
)
parser.add_argument(
'--project-id',
metavar='project_id',
help='ID of the consuming project. '
'This option is required starting from '
'``--os-placement-api-version 1.8``.',
required=self.compare_version(version.ge('1.8'))
)
parser.add_argument(
'--user-id',
metavar='user_id',
help='ID of the consuming user. '
'This option is required starting from '
'``--os-placement-api-version 1.8``.',
required=self.compare_version(version.ge('1.8'))
)
return parser
def take_action(self, parsed_args):
@ -83,7 +106,15 @@ class SetAllocation(command.Lister):
for rp, resources in allocations.items()]
url = BASE_URL + '/' + parsed_args.uuid
http.request('PUT', url, json={'allocations': allocations})
payload = {'allocations': allocations}
if self.compare_version(version.ge('1.8')):
payload['project_id'] = parsed_args.project_id
payload['user_id'] = parsed_args.user_id
elif parsed_args.project_id or parsed_args.user_id:
self.log.warning('--project-id and --user-id options do not '
'affect allocation for '
'--os-placement-api-version less than 1.8')
http.request('PUT', url, json=payload)
per_provider = http.request('GET', url).json()['allocations'].items()
allocs = [dict(resource_provider=k, **v) for k, v in per_provider]
@ -93,7 +124,7 @@ class SetAllocation(command.Lister):
class ShowAllocation(command.Lister):
"""Show resource allocations for a given consumer"""
"""Show resource allocations for a given consumer."""
def get_parser(self, prog_name):
parser = super(ShowAllocation, self).get_parser(prog_name)
@ -119,7 +150,7 @@ class ShowAllocation(command.Lister):
class DeleteAllocation(command.Command):
"""Delete a resource allocation for a given consumer"""
"""Delete a resource allocation for a given consumer."""
def get_parser(self, prog_name):
parser = super(DeleteAllocation, self).get_parser(prog_name)

View File

@ -13,11 +13,58 @@
from osc_lib.command import command
from osc_lib import utils
from osc_placement import version
BASE_URL = '/resource_providers/{uuid}/usages'
USAGES_URL = '/usages'
FIELDS = ('resource_class', 'usage')
class ResourceShowUsage(command.Lister, version.CheckerMixin):
"""Show resource usages for a project (and optionally user) per class.
Gives a report of usage information for resources associated with the
project identified by the ``project_id`` argument and user identified by
the ``--user-id`` option.
This command requires at least ``--os-placement-api-version 1.9``.
"""
def get_parser(self, prog_name):
parser = super(ResourceShowUsage, self).get_parser(prog_name)
parser.add_argument(
'project_id',
metavar='<project-uuid>',
help='ID of the project.'
)
parser.add_argument(
'--user-id',
metavar='<user-uuid>',
help='ID of the user.'
)
return parser
@version.check(version.ge('1.9'))
def take_action(self, parsed_args):
http = self.app.client_manager.placement
url = USAGES_URL
params = {'project_id': parsed_args.project_id}
if parsed_args.user_id:
params['user_id'] = parsed_args.user_id
per_class = http.request(
'GET', url, params=params).json()['usages']
usages = [{'resource_class': k, 'usage': v}
for k, v in per_class.items()]
rows = (utils.get_dict_properties(u, FIELDS) for u in usages)
return FIELDS, rows
class ShowUsage(command.Lister):
"""Show resource usages per class for a given resource provider."""

View File

@ -115,12 +115,18 @@ class BaseTestCase(base.BaseTestCase):
use_json=True
)
def resource_allocation_set(self, consumer_uuid, allocations):
def resource_allocation_set(self, consumer_uuid, allocations,
project_id=None, user_id=None,
use_json=True):
cmd = 'resource provider allocation set {allocs} {uuid}'.format(
uuid=consumer_uuid,
allocs=' '.join('--allocation {}'.format(a) for a in allocations)
)
result = self.openstack(cmd, use_json=True)
if project_id:
cmd += ' --project-id %s' % project_id
if user_id:
cmd += ' --user-id %s' % user_id
result = self.openstack(cmd, use_json=use_json)
def cleanup(uuid):
try:
@ -169,6 +175,12 @@ class BaseTestCase(base.BaseTestCase):
return self.openstack('resource provider usage show ' + uuid,
use_json=True)
def resource_show_usage(self, project_id, user_id=None):
cmd = 'resource usage show %s' % project_id
if user_id:
cmd += ' --user-id %s' % user_id
return self.openstack(cmd, use_json=True)
def resource_provider_aggregate_list(self, uuid):
return self.openstack('resource provider aggregate list ' + uuid,
use_json=True)

View File

@ -52,6 +52,19 @@ class TestAllocation(base.BaseTestCase):
self.assertEqual(expected, created_alloc)
self.assertEqual(expected, retrieved_alloc)
# Test that specifying --project-id and --user-id before microversion
# 1.8 does not result in an error (they will be ignored). We have
# to specify use_json=False because there will be a warning in the
# output which can't be json-decoded.
output = self.resource_allocation_set(
consumer_uuid,
['rp={},VCPU=2'.format(self.rp1['uuid']),
'rp={},MEMORY_MB=512'.format(self.rp1['uuid'])],
project_id='fake-project', user_id='fake-user', use_json=False)
self.assertIn(
'--project-id and --user-id options do not affect allocation for '
'--os-placement-api-version less than 1.8', output)
def test_allocation_create_empty(self):
consumer_uuid = str(uuid.uuid4())
@ -81,3 +94,35 @@ class TestAllocation(base.BaseTestCase):
exc = self.assertRaises(subprocess.CalledProcessError,
self.resource_allocation_delete, consumer_uuid)
self.assertIn(msg, exc.output.decode('utf-8'))
class TestAllocation18(base.BaseTestCase):
VERSION = '1.8'
def test_allocation_create(self):
consumer_uuid = str(uuid.uuid4())
project_id = str(uuid.uuid4())
user_id = str(uuid.uuid4())
rp1 = self.resource_provider_create()
self.resource_inventory_set(
rp1['uuid'],
'VCPU=4',
'VCPU:max_unit=4',
'MEMORY_MB=1024',
'MEMORY_MB:max_unit=1024')
created_alloc = self.resource_allocation_set(
consumer_uuid,
['rp={},VCPU=2'.format(rp1['uuid']),
'rp={},MEMORY_MB=512'.format(rp1['uuid'])],
project_id=project_id, user_id=user_id
)
retrieved_alloc = self.resource_allocation_show(consumer_uuid)
expected = [
{'resource_provider': rp1['uuid'],
'generation': 2,
'resources': {'VCPU': 2, 'MEMORY_MB': 512}}
]
self.assertEqual(expected, created_alloc)
self.assertEqual(expected, retrieved_alloc)

View File

@ -58,3 +58,39 @@ class TestUsage(base.BaseTestCase):
rp = self.resource_provider_create()
self.assertEqual([], self.resource_provider_show_usage(rp['uuid']))
class TestResourceUsage(base.BaseTestCase):
VERSION = '1.9'
def test_usage_by_project_id_user_id(self):
c1 = str(uuid.uuid4())
c2 = str(uuid.uuid4())
c3 = str(uuid.uuid4())
p1 = str(uuid.uuid4())
p2 = str(uuid.uuid4())
u1 = str(uuid.uuid4())
u2 = str(uuid.uuid4())
rp = self.resource_provider_create()
self.resource_inventory_set(rp['uuid'], 'VCPU=16')
self.resource_allocation_set(
c1, ['rp={},VCPU=2'.format(rp['uuid'])], project_id=p1, user_id=u1)
self.resource_allocation_set(
c2, ['rp={},VCPU=4'.format(rp['uuid'])], project_id=p2, user_id=u1)
self.resource_allocation_set(
c3, ['rp={},VCPU=6'.format(rp['uuid'])], project_id=p1, user_id=u2)
# Show usage on the resource provider for all consumers.
self.assertEqual(
12, self.resource_provider_show_usage(uuid=rp['uuid'])[0]['usage'])
# Show usage for project p1.
self.assertEqual(
8, self.resource_show_usage(project_id=p1)[0]['usage'])
# Show usage for project p1 and user u1.
self.assertEqual(
2, self.resource_show_usage(
project_id=p1, user_id=u1)[0]['usage'])
# Show usage for project p2.
self.assertEqual(
4, self.resource_show_usage(project_id=p2)[0]['usage'])

View File

@ -23,6 +23,8 @@ SUPPORTED_VERSIONS = [
'1.5',
'1.6',
'1.7',
'1.8',
'1.9',
]

View File

@ -0,0 +1,18 @@
---
features:
- |
The ``openstack resource provider allocation set`` command now supports
microversion `1.8`_. Specifically from 1.8 it is necessary to specify
``--user-id`` and ``--project-id`` arguments when setting allocations.
The ``openstack resource usage show`` command is
available starting from microversion `1.9`_. It is possible to
show usages for a project and user.
See the command documentation for `allocation set`_ and
`resource usage show`_ for more details.
.. _1.8: https://docs.openstack.org/nova/latest/user/placement.html#require-placement-project-id-user-id-in-put-allocations
.. _1.9: https://docs.openstack.org/nova/latest/user/placement.html#add-get-usages
.. _allocation set: https://docs.openstack.org/osc-placement/latest/cli/index.html#resource-provider-allocation-set
.. _resource usage show: https://docs.openstack.org/osc-placement/latest/cli/index.html#resource-usage-show

View File

@ -35,7 +35,8 @@ openstack.placement.v1 =
resource_provider_show = osc_placement.resources.resource_provider:ShowResourceProvider
resource_provider_set = osc_placement.resources.resource_provider:SetResourceProvider
resource_provider_delete = osc_placement.resources.resource_provider:DeleteResourceProvider
resource provider_usage_show = osc_placement.resources.usage:ShowUsage
resource_provider_usage_show = osc_placement.resources.usage:ShowUsage
resource_usage_show = osc_placement.resources.usage:ResourceShowUsage
resource_provider_inventory_set = osc_placement.resources.inventory:SetInventory
resource_provider_inventory_class_set = osc_placement.resources.inventory:SetClassInventory
resource_provider_inventory_list = osc_placement.resources.inventory:ListInventory