CLI for resource providers
Co-Authored-by: Andrey Volkov <avolkov@mirantis.com> Blueprint: placement-osc-plugin Change-Id: Ifa82bc0ae5c067fd39a932e9e0ec02d5987ead46
This commit is contained in:
parent
5769a511e9
commit
1b1ae94ed8
60
osc_placement/http.py
Normal file
60
osc_placement/http.py
Normal file
@ -0,0 +1,60 @@
|
||||
# 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.
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
|
||||
import keystoneauth1.exceptions.http as ks_exceptions
|
||||
import osc_lib.exceptions as exceptions
|
||||
import six
|
||||
|
||||
|
||||
_http_error_to_exc = {
|
||||
cls.http_status: cls
|
||||
for cls in exceptions.ClientException.__subclasses__()
|
||||
}
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _wrap_http_exceptions():
|
||||
"""Reraise osc-lib exceptions with detailed messages."""
|
||||
|
||||
try:
|
||||
yield
|
||||
except ks_exceptions.HttpError as exc:
|
||||
detail = json.loads(exc.response.content)['errors'][0]['detail']
|
||||
msg = detail.split('\n')[-1].strip()
|
||||
exc_class = _http_error_to_exc.get(exc.http_status,
|
||||
exceptions.CommandError)
|
||||
|
||||
six.raise_from(exc_class(exc.http_status, msg), exc)
|
||||
|
||||
|
||||
class SessionClient(object):
|
||||
def __init__(self, session, ks_filter, api_version='1.0'):
|
||||
self.session = session
|
||||
self.ks_filter = ks_filter
|
||||
self.api_version = api_version
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
version = kwargs.pop('version', None)
|
||||
api_version = (self.ks_filter['service_type'] + ' ' +
|
||||
(version or self.api_version))
|
||||
headers = kwargs.pop('headers', {})
|
||||
headers.setdefault('OpenStack-API-Version', api_version)
|
||||
headers.setdefault('Accept', 'application/json')
|
||||
|
||||
with _wrap_http_exceptions():
|
||||
return self.session.request(url, method,
|
||||
headers=headers,
|
||||
endpoint_filter=self.ks_filter,
|
||||
**kwargs)
|
@ -21,7 +21,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
API_NAME = 'placement'
|
||||
API_VERSION_OPTION = 'os_placement_api_version'
|
||||
API_VERSIONS = {'1.0': 'osc_placement.plugin.Client'}
|
||||
API_VERSIONS = {'1.0': 'osc_placement.http.SessionClient'}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
@ -31,10 +31,14 @@ def make_client(instance):
|
||||
API_VERSIONS
|
||||
)
|
||||
|
||||
ks_filter = {'service_type': API_NAME,
|
||||
'region_name': instance._region_name,
|
||||
'interface': instance.interface}
|
||||
|
||||
LOG.debug('Instantiating placement client: %s', client_class)
|
||||
# TODO(rpodolyaka): add version negotiation
|
||||
return client_class(session=instance.session,
|
||||
region_name=instance._region_name,
|
||||
interface=instance.interface,
|
||||
ks_filter=ks_filter,
|
||||
api_version=instance._api_version[API_NAME])
|
||||
|
||||
|
||||
@ -49,14 +53,3 @@ def build_option_parser(parser):
|
||||
help='Placement API version, default=1.0'
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, session, region_name, interface, api_version='1.0'):
|
||||
self.session = session
|
||||
self.api_version = api_version
|
||||
self.ks_filter = {
|
||||
'service_type': 'placement',
|
||||
'region_name': region_name,
|
||||
'interface': interface,
|
||||
}
|
||||
|
0
osc_placement/resources/__init__.py
Normal file
0
osc_placement/resources/__init__.py
Normal file
39
osc_placement/resources/common.py
Normal file
39
osc_placement/resources/common.py
Normal file
@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
|
||||
import six
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
|
||||
def encode(value, encoding='utf-8'):
|
||||
"""Return a byte repr of a string for a given encoding.
|
||||
|
||||
Byte strings and values of other types are returned as is.
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(value, six.text_type):
|
||||
return value.encode(encoding)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def url_with_filters(url, filters=None):
|
||||
"""Add a percent-encoded string of filters (a dict) to a base url."""
|
||||
|
||||
if filters:
|
||||
filters = [(encode(k), encode(v)) for k, v in filters.items()]
|
||||
|
||||
urlencoded_filters = urlparse.urlencode(filters)
|
||||
url = urlparse.urljoin(url, '?' + urlencoded_filters)
|
||||
|
||||
return url
|
159
osc_placement/resources/resource_provider.py
Normal file
159
osc_placement/resources/resource_provider.py
Normal file
@ -0,0 +1,159 @@
|
||||
# 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 osc_lib.command import command
|
||||
from osc_lib import utils
|
||||
|
||||
from osc_placement.resources import common
|
||||
|
||||
|
||||
BASE_URL = '/resource_providers'
|
||||
FIELDS = ('uuid', 'name', 'generation')
|
||||
|
||||
|
||||
class CreateResourceProvider(command.ShowOne):
|
||||
"""Create a new resource provider"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateResourceProvider, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--uuid',
|
||||
metavar='<uuid>',
|
||||
help='UUID of the resource provider'
|
||||
)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help='Name of the resource provider'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
http = self.app.client_manager.placement
|
||||
|
||||
data = {'name': parsed_args.name}
|
||||
|
||||
if 'uuid' in parsed_args and parsed_args.uuid:
|
||||
data['uuid'] = parsed_args.uuid
|
||||
|
||||
resp = http.request('POST', BASE_URL, json=data)
|
||||
resource = http.request('GET', resp.headers['Location']).json()
|
||||
return FIELDS, utils.get_dict_properties(resource, FIELDS)
|
||||
|
||||
|
||||
class ListResourceProvider(command.Lister):
|
||||
"""List resource providers"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListResourceProvider, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--uuid',
|
||||
metavar='<uuid>',
|
||||
help='UUID of the resource provider'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help='Name of the resource provider'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
http = self.app.client_manager.placement
|
||||
|
||||
filters = {}
|
||||
if 'name' in parsed_args and parsed_args.name:
|
||||
filters['name'] = parsed_args.name
|
||||
if 'uuid' in parsed_args and parsed_args.uuid:
|
||||
filters['uuid'] = parsed_args.uuid
|
||||
|
||||
url = common.url_with_filters(BASE_URL, filters)
|
||||
resources = http.request('GET', url).json()['resource_providers']
|
||||
rows = (utils.get_dict_properties(r, FIELDS) for r in resources)
|
||||
return FIELDS, rows
|
||||
|
||||
|
||||
class ShowResourceProvider(command.ShowOne):
|
||||
"""Show resource provider details"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowResourceProvider, self).get_parser(prog_name)
|
||||
# TODO(avolkov): show by uuid or name
|
||||
parser.add_argument(
|
||||
'uuid',
|
||||
metavar='<uuid>',
|
||||
help='UUID of the resource provider'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
http = self.app.client_manager.placement
|
||||
|
||||
url = BASE_URL + '/' + parsed_args.uuid
|
||||
resource = http.request('GET', url).json()
|
||||
return FIELDS, utils.get_dict_properties(resource, FIELDS)
|
||||
|
||||
|
||||
class SetResourceProvider(command.ShowOne):
|
||||
"""Update an existing resource provider"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SetResourceProvider, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'uuid',
|
||||
metavar='<uuid>',
|
||||
help='UUID of the resource provider'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help='A new name of the resource provider',
|
||||
required=True
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
http = self.app.client_manager.placement
|
||||
|
||||
url = BASE_URL + '/' + parsed_args.uuid
|
||||
resource = http.request('PUT', url,
|
||||
json={'name': parsed_args.name}).json()
|
||||
return FIELDS, utils.get_dict_properties(resource, FIELDS)
|
||||
|
||||
|
||||
class DeleteResourceProvider(command.Command):
|
||||
"""Delete a resource provider"""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeleteResourceProvider, self).get_parser(prog_name)
|
||||
|
||||
# TODO(avolkov): delete by uuid or name
|
||||
parser.add_argument(
|
||||
'uuid',
|
||||
metavar='<uuid>',
|
||||
help='UUID of the resource provider'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
http = self.app.client_manager.placement
|
||||
|
||||
url = BASE_URL + '/' + parsed_args.uuid
|
||||
http.request('DELETE', url)
|
81
osc_placement/tests/functional/base.py
Normal file
81
osc_placement/tests/functional/base.py
Normal file
@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
from oslotest import base
|
||||
|
||||
|
||||
RP_PREFIX = 'osc-placement-functional-tests-'
|
||||
|
||||
|
||||
class BaseTestCase(base.BaseTestCase):
|
||||
@staticmethod
|
||||
def openstack(cmd, may_fail=False, use_json=False):
|
||||
try:
|
||||
to_exec = ['openstack'] + cmd.split()
|
||||
if use_json:
|
||||
to_exec += ['-f', 'json']
|
||||
|
||||
output = subprocess.check_output(to_exec, stderr=subprocess.STDOUT)
|
||||
result = (output or b'').decode('utf-8')
|
||||
except subprocess.CalledProcessError:
|
||||
if not may_fail:
|
||||
raise
|
||||
|
||||
if use_json:
|
||||
return json.loads(result)
|
||||
else:
|
||||
return result
|
||||
|
||||
def resource_provider_create(self, name=''):
|
||||
if not name:
|
||||
random_part = ''.join(random.choice(string.ascii_letters)
|
||||
for i in range(10))
|
||||
name = RP_PREFIX + random_part
|
||||
|
||||
res = self.openstack('resource provider create ' + name,
|
||||
use_json=True)
|
||||
|
||||
def cleanup():
|
||||
try:
|
||||
self.resource_provider_delete(res['uuid'])
|
||||
except subprocess.CalledProcessError as exc:
|
||||
# may have already been deleted by a test case
|
||||
err_message = exc.output.decode('utf-8').lower()
|
||||
if 'no resource provider' not in err_message:
|
||||
raise
|
||||
self.addCleanup(cleanup)
|
||||
|
||||
return res
|
||||
|
||||
def resource_provider_set(self, uuid, name):
|
||||
to_exec = 'resource provider set ' + uuid + ' --name ' + name
|
||||
return self.openstack(to_exec, use_json=True)
|
||||
|
||||
def resource_provider_show(self, uuid):
|
||||
return self.openstack('resource provider show ' + uuid, use_json=True)
|
||||
|
||||
def resource_provider_list(self, uuid=None, name=None):
|
||||
to_exec = 'resource provider list'
|
||||
if uuid:
|
||||
to_exec += ' --uuid ' + uuid
|
||||
if name:
|
||||
to_exec += ' --name ' + name
|
||||
|
||||
return self.openstack(to_exec, use_json=True)
|
||||
|
||||
def resource_provider_delete(self, uuid):
|
||||
return self.openstack('resource provider delete ' + uuid)
|
120
osc_placement/tests/functional/test_resource_provider.py
Normal file
120
osc_placement/tests/functional/test_resource_provider.py
Normal file
@ -0,0 +1,120 @@
|
||||
# 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.
|
||||
|
||||
import operator
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
import six
|
||||
|
||||
from osc_placement.tests.functional import base
|
||||
|
||||
|
||||
class TestResourceProvider(base.BaseTestCase):
|
||||
def test_resource_provider_create(self):
|
||||
created = self.resource_provider_create('test_rp_creation')
|
||||
self.assertEqual('test_rp_creation', created['name'])
|
||||
|
||||
retrieved = self.resource_provider_show(created['uuid'])
|
||||
self.assertEqual(created, retrieved)
|
||||
|
||||
def test_resource_provider_delete(self):
|
||||
created = self.resource_provider_create()
|
||||
|
||||
before_delete = self.resource_provider_list(uuid=created['uuid'])
|
||||
self.assertEqual([created['uuid']],
|
||||
[rp['uuid'] for rp in before_delete])
|
||||
|
||||
self.resource_provider_delete(created['uuid'])
|
||||
after_delete = self.resource_provider_list(uuid=created['uuid'])
|
||||
self.assertEqual([], after_delete)
|
||||
|
||||
def test_resource_provider_delete_not_found(self):
|
||||
rp_uuid = six.text_type(uuid.uuid4())
|
||||
msg = 'No resource provider with uuid ' + rp_uuid + ' found'
|
||||
|
||||
exc = self.assertRaises(subprocess.CalledProcessError,
|
||||
self.resource_provider_delete, rp_uuid)
|
||||
self.assertIn(msg, exc.output.decode('utf-8'))
|
||||
|
||||
def test_resource_provider_set(self):
|
||||
created = self.resource_provider_create(name='test_rp_orig_name')
|
||||
|
||||
before_update = self.resource_provider_show(created['uuid'])
|
||||
self.assertEqual('test_rp_orig_name', before_update['name'])
|
||||
self.assertEqual(0, before_update['generation'])
|
||||
|
||||
self.resource_provider_set(created['uuid'], name='test_rp_new_name')
|
||||
after_update = self.resource_provider_show(created['uuid'])
|
||||
self.assertEqual('test_rp_new_name', after_update['name'])
|
||||
self.assertEqual(0, after_update['generation'])
|
||||
|
||||
def test_resource_provider_set_not_found(self):
|
||||
rp_uuid = six.text_type(uuid.uuid4())
|
||||
msg = 'No resource provider with uuid ' + rp_uuid + ' found'
|
||||
|
||||
exc = self.assertRaises(subprocess.CalledProcessError,
|
||||
self.resource_provider_set, rp_uuid, 'test')
|
||||
self.assertIn(msg, exc.output.decode('utf-8'))
|
||||
|
||||
def test_resource_provider_show(self):
|
||||
created = self.resource_provider_create()
|
||||
|
||||
retrieved = self.resource_provider_show(created['uuid'])
|
||||
self.assertEqual(created, retrieved)
|
||||
|
||||
def test_resource_provider_show_not_found(self):
|
||||
rp_uuid = six.text_type(uuid.uuid4())
|
||||
msg = 'No resource provider with uuid ' + rp_uuid + ' found'
|
||||
|
||||
exc = self.assertRaises(subprocess.CalledProcessError,
|
||||
self.resource_provider_show, rp_uuid)
|
||||
self.assertIn(msg, exc.output.decode('utf-8'))
|
||||
|
||||
def test_resource_provider_list(self):
|
||||
rp1 = self.resource_provider_create()
|
||||
rp2 = self.resource_provider_create()
|
||||
|
||||
expected_full = sorted([rp1, rp2], key=operator.itemgetter('uuid'))
|
||||
self.assertEqual(
|
||||
expected_full,
|
||||
sorted([rp for rp in self.resource_provider_list()
|
||||
if rp['name'] in (rp1['name'], rp2['name'])],
|
||||
key=operator.itemgetter('uuid'))
|
||||
)
|
||||
|
||||
def test_resource_provider_list_by_name(self):
|
||||
rp1 = self.resource_provider_create()
|
||||
self.resource_provider_create()
|
||||
|
||||
expected_filtered_by_name = [rp1]
|
||||
self.assertEqual(
|
||||
expected_filtered_by_name,
|
||||
[rp for rp in self.resource_provider_list(name=rp1['name'])]
|
||||
)
|
||||
|
||||
def test_resource_provider_list_by_uuid(self):
|
||||
rp1 = self.resource_provider_create()
|
||||
self.resource_provider_create()
|
||||
|
||||
expected_filtered_by_uuid = [rp1]
|
||||
self.assertEqual(
|
||||
expected_filtered_by_uuid,
|
||||
[rp for rp in self.resource_provider_list(uuid=rp1['uuid'])]
|
||||
)
|
||||
|
||||
def test_resource_provider_list_empty(self):
|
||||
by_name = self.resource_provider_list(name='some_non_existing_name')
|
||||
self.assertEqual([], by_name)
|
||||
|
||||
by_uuid = self.resource_provider_list(uuid=str(uuid.uuid4()))
|
||||
self.assertEqual([], by_uuid)
|
56
osc_placement/tests/unit/test_common.py
Normal file
56
osc_placement/tests/unit/test_common.py
Normal file
@ -0,0 +1,56 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# 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.
|
||||
|
||||
import collections
|
||||
|
||||
import oslotest.base as base
|
||||
|
||||
import osc_placement.resources.common as common
|
||||
|
||||
|
||||
class TestCommon(base.BaseTestCase):
|
||||
def test_encode(self):
|
||||
self.assertEqual(u'привет'.encode('utf-8'),
|
||||
common.encode(u'привет'))
|
||||
|
||||
def test_encode_custom_encoding(self):
|
||||
self.assertEqual(u'привет'.encode('utf-16'),
|
||||
common.encode(u'привет', 'utf-16'))
|
||||
|
||||
def test_encode_non_string(self):
|
||||
self.assertEqual(b'bytesvalue',
|
||||
common.encode(b'bytesvalue'))
|
||||
|
||||
def test_url_with_filters(self):
|
||||
base_url = '/resource_providers'
|
||||
expected = '/resource_providers?name=test&uuid=123456'
|
||||
|
||||
filters = collections.OrderedDict([('name', 'test'), ('uuid', 123456)])
|
||||
|
||||
actual = common.url_with_filters(base_url, filters)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_url_with_filters_empty(self):
|
||||
base_url = '/resource_providers'
|
||||
|
||||
self.assertEqual(base_url, common.url_with_filters(base_url))
|
||||
self.assertEqual(base_url, common.url_with_filters(base_url, {}))
|
||||
|
||||
def test_url_with_filters_unicode_string(self):
|
||||
base_url = '/resource_providers'
|
||||
expected = ('/resource_providers?'
|
||||
'name=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82')
|
||||
|
||||
actual = common.url_with_filters(base_url, {'name': u'привет'})
|
||||
self.assertEqual(expected, actual)
|
43
osc_placement/tests/unit/test_http.py
Normal file
43
osc_placement/tests/unit/test_http.py
Normal file
@ -0,0 +1,43 @@
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
import keystoneauth1.exceptions.http as ks_exceptions
|
||||
import osc_lib.exceptions as exceptions
|
||||
import oslotest.base as base
|
||||
|
||||
import osc_placement.http as http
|
||||
|
||||
|
||||
class TestSessionClient(base.BaseTestCase):
|
||||
def test_wrap_http_exceptions(self):
|
||||
def go():
|
||||
with http._wrap_http_exceptions():
|
||||
error = {
|
||||
"errors": [
|
||||
{"status": 404,
|
||||
"detail": ("The resource could not be found.\n\n"
|
||||
"No resource provider with uuid 123 "
|
||||
"found for delete")}
|
||||
]
|
||||
}
|
||||
response = mock.Mock(content=json.dumps(error))
|
||||
raise ks_exceptions.NotFound(response=response)
|
||||
|
||||
exc = self.assertRaises(exceptions.NotFound, go)
|
||||
self.assertEqual(404, exc.http_status)
|
||||
self.assertIn('No resource provider with uuid 123 found',
|
||||
six.text_type(exc))
|
@ -27,14 +27,17 @@ class TestPlugin(base.BaseTestCase):
|
||||
args = parser.parse_args(['--os-placement-api-version', '1.0'])
|
||||
self.assertEqual('1.0', args.os_placement_api_version)
|
||||
|
||||
def test_make_client(self):
|
||||
@mock.patch('osc_placement.http.SessionClient')
|
||||
def test_make_client(self, mock_session_client):
|
||||
instance = mock.Mock(_api_version={'placement': '1.0'})
|
||||
client = plugin.make_client(instance)
|
||||
|
||||
self.assertIsInstance(client, plugin.Client)
|
||||
self.assertIs(client.session, instance.session)
|
||||
self.assertEqual('1.0', client.api_version)
|
||||
self.assertEqual({'service_type': 'placement',
|
||||
'region_name': instance._region_name,
|
||||
'interface': instance.interface},
|
||||
client.ks_filter)
|
||||
plugin.make_client(instance)
|
||||
mock_session_client.assert_called_with(
|
||||
session=instance.session,
|
||||
ks_filter={
|
||||
'service_type': 'placement',
|
||||
'region_name': instance._region_name,
|
||||
'interface': instance.interface
|
||||
},
|
||||
api_version='1.0'
|
||||
)
|
||||
|
@ -26,6 +26,13 @@ packages =
|
||||
openstack.cli.extension =
|
||||
placement = osc_placement.plugin
|
||||
|
||||
openstack.placement.v1 =
|
||||
resource_provider_create = osc_placement.resources.resource_provider:CreateResourceProvider
|
||||
resource_provider_list = osc_placement.resources.resource_provider:ListResourceProvider
|
||||
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
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
|
Loading…
Reference in New Issue
Block a user