Add placement client for basic GET operations

The goal of this placement client for tempest is to make possible the
end-to-end testing of the placement based bandwidth feature (see the
nova and neutron specs: https://review.openstack.org/502306
https://review.openstack.org/508149)
The minimum placement microversion is 1.29, and the following operations
are supported: list_allocation_candidates, list_allocations.

Change-Id: I0cf1caadeb40761ec79338510919f12baf2d8f56
Partial-Bug: #1578989
See-Also: https://review.openstack.org/502306 (nova spec)
See-Also: https://review.openstack.org/508149 (neutron spec)
This commit is contained in:
Lajos Katona 2018-11-30 14:54:12 +01:00
parent ccdd729aaf
commit ceb882169a
8 changed files with 252 additions and 0 deletions

View File

@ -0,0 +1,17 @@
---
features:
- |
Add basic read-only Placement client to Tempest to make possible the
testing of the placement based bandwidth allocation feature.
The following API calls are available for tempest from now:
* GET /allocation_candidates
* GET /allocations/{consumer_uuid}
Add new config group ``placement``, with the config options:
* ``endpoint_type`` to use for communication with placement service.
* ``catalog_type`` of the placement service.
* ``region`` as the placement region name to use.
* ``min_microversion`` and ``max_microversion`` as the range between
placement API requests are sent.

View File

@ -365,6 +365,38 @@ ComputeGroup = [
"with format 'X.Y' or string 'latest'"),
]
placement_group = cfg.OptGroup(name='placement',
title='Placement Service Options')
PlacementGroup = [
cfg.StrOpt('endpoint_type',
default='public',
choices=['public', 'admin', 'internal'],
help="The endpoint type to use for the placement service."),
cfg.StrOpt('catalog_type',
default='placement',
help="Catalog type of the Placement service."),
cfg.StrOpt('region',
default='RegionOne',
help="The placement region name to use. If empty, the value "
"of [identity]/region is used instead. If no such region "
"is found in the service catalog, the first region found "
"is used."),
cfg.StrOpt('min_microversion',
default=None,
help="Lower version of the test target microversion range. "
"The format is 'X.Y', where 'X' and 'Y' are int values. "
"Valid values are string with format 'X.Y' or string "
"'latest'"),
cfg.StrOpt('max_microversion',
default=None,
help="Upper version of the test target microversion range. "
"The format is 'X.Y', where 'X' and 'Y' are int values. "
"Valid values are string with format 'X.Y' or string "
"'latest'"),
]
compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
title="Enabled Compute Service Features")
@ -1096,6 +1128,7 @@ _opts = [
(scenario_group, ScenarioGroup),
(service_available_group, ServiceAvailableGroup),
(debug_group, DebugGroup),
(placement_group, PlacementGroup),
(None, DefaultGroup)
]

View File

@ -32,6 +32,7 @@ from tempest.lib.services import identity
from tempest.lib.services import image
from tempest.lib.services import network
from tempest.lib.services import object_storage
from tempest.lib.services import placement
from tempest.lib.services import volume
warnings.simplefilter("once")
@ -46,6 +47,7 @@ def tempest_modules():
"""
return {
'compute': compute,
'placement': placement,
'identity.v2': identity.v2,
'identity.v3': identity.v3,
'image.v1': image.v1,

View File

@ -0,0 +1,18 @@
# Copyright (c) 2019 Ericsson
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest.lib.services.placement.placement_client import \
PlacementClient
__all__ = ['PlacementClient']

View File

@ -0,0 +1,43 @@
# Copyright (c) 2019 Ericsson
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest.lib.common import api_version_utils
from tempest.lib.common import rest_client
PLACEMENT_MICROVERSION = None
class BasePlacementClient(rest_client.RestClient):
api_microversion_header_name = 'OpenStack-API-Version'
version_header_value = 'placement %s'
def get_headers(self):
headers = super(BasePlacementClient, self).get_headers()
if PLACEMENT_MICROVERSION:
headers[self.api_microversion_header_name] = \
self.version_header_value % PLACEMENT_MICROVERSION
return headers
def request(self, method, url, extra_headers=False, headers=None,
body=None, chunked=False):
resp, resp_body = super(BasePlacementClient, self).request(
method, url, extra_headers, headers, body, chunked)
if (PLACEMENT_MICROVERSION and
PLACEMENT_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
api_version_utils.assert_version_header_matches_request(
self.api_microversion_header_name,
self.version_header_value % PLACEMENT_MICROVERSION,
resp)
return resp, resp_body

View File

@ -0,0 +1,50 @@
# Copyright (c) 2019 Ericsson
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
from tempest.lib.services.placement import base_placement_client
class PlacementClient(base_placement_client.BasePlacementClient):
def list_allocation_candidates(self, **params):
"""List allocation candidates.
For full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/placement/#list-allocation-candidates
"""
url = '/allocation_candidates'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def list_allocations(self, consumer_uuid):
"""List all allocation records for the consumer.
For full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/placement/#list-allocations
"""
url = '/allocations/%s' % consumer_uuid
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,89 @@
# Copyright (c) 2019 Ericsson
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest.lib.services.placement import placement_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
class TestPlacementClient(base.BaseServiceTest):
FAKE_ALLOCATION_CANDIDATES = {
'allocation_requests': [
{'allocations': {
'rp-uuid': {'resources': {'VCPU': 42}}
}}
],
'provider_summaries': {
'rp-uuid': {
'resources': {
'VCPU': {'used': 0, 'capacity': 64},
'MEMORY_MB': {'capacity': 11196, 'used': 0},
'DISK_GB': {'capacity': 19, 'used': 0}
},
'traits': ["HW_CPU_X86_SVM"],
}
}
}
FAKE_ALLOCATIONS = {
'allocations': {
'rp-uuid-1': {
'resources': {
'NET_BW_IGR_KILOBIT_PER_SEC': 1
},
'generation': 14
},
'rp-uuid2': {
'resources': {
'MEMORY_MB': 256,
'VCPU': 1
},
'generation': 9
}
}
}
def setUp(self):
super(TestPlacementClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = placement_client.PlacementClient(
fake_auth, 'placement', 'regionOne')
def _test_list_allocation_candidates(self, bytes_body=False):
self.check_service_client_function(
self.client.list_allocation_candidates,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_ALLOCATION_CANDIDATES,
to_utf=bytes_body,
**{'resources1': 'NET_BW_IGR_KILOBIT_PER_SEC:1'})
def test_list_allocation_candidates_with_str_body(self):
self._test_list_allocation_candidates()
def test_list_allocation_candidates_with_bytes_body(self):
self._test_list_allocation_candidates(bytes_body=True)
def _test_list_allocations(self, bytes_body=False):
self.check_service_client_function(
self.client.list_allocations,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_ALLOCATIONS,
to_utf=bytes_body,
**{'consumer_uuid': 'foo-bar'})
def test_list_allocations_with_str_body(self):
self._test_list_allocations()
def test_list_allocations_with_bytes_body(self):
self._test_list_allocations(bytes_body=True)