Add create and delete operation to client wrapper
Make client wrapper support create and delete operation so we can create and delete aggregate in top OpenStack layer via DAL. Change-Id: I13b7a0256e366f834965b817a6cd0237e52a6002
This commit is contained in:
parent
80d037fb4e
commit
3d9d2daf3b
|
@ -13,8 +13,10 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
import six
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from keystoneclient.auth.identity import v3 as auth_identity
|
from keystoneclient.auth.identity import v3 as auth_identity
|
||||||
|
@ -67,10 +69,35 @@ cfg.CONF.register_opts(client_opts, group=client_opt_group)
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_operation(operation_name):
|
||||||
|
def handle_func(func):
|
||||||
|
@six.wraps(func)
|
||||||
|
def handle_args(*args, **kwargs):
|
||||||
|
instance, resource, context = args[:3]
|
||||||
|
if resource not in instance.operation_resources_map[
|
||||||
|
operation_name]:
|
||||||
|
raise exception.ResourceNotSupported(resource, operation_name)
|
||||||
|
retries = 1
|
||||||
|
for _ in xrange(retries + 1):
|
||||||
|
try:
|
||||||
|
service = instance.resource_service_map[resource]
|
||||||
|
instance._ensure_endpoint_set(context, service)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except exception.EndpointNotAvailable as e:
|
||||||
|
if cfg.CONF.client.auto_refresh_endpoint:
|
||||||
|
LOG.warn(e.message + ', update endpoint and try again')
|
||||||
|
instance._update_endpoint_from_keystone(context, True)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return handle_args
|
||||||
|
return handle_func
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.auth_url = cfg.CONF.client.auth_url
|
self.auth_url = cfg.CONF.client.auth_url
|
||||||
self.resource_service_map = {}
|
self.resource_service_map = {}
|
||||||
|
self.operation_resources_map = collections.defaultdict(set)
|
||||||
self.service_handle_map = {}
|
self.service_handle_map = {}
|
||||||
for _, handle_class in inspect.getmembers(resource_handle):
|
for _, handle_class in inspect.getmembers(resource_handle):
|
||||||
if not inspect.isclass(handle_class):
|
if not inspect.isclass(handle_class):
|
||||||
|
@ -81,8 +108,16 @@ class Client(object):
|
||||||
self.service_handle_map[handle_obj.service_type] = handle_obj
|
self.service_handle_map[handle_obj.service_type] = handle_obj
|
||||||
for resource in handle_obj.support_resource:
|
for resource in handle_obj.support_resource:
|
||||||
self.resource_service_map[resource] = handle_obj.service_type
|
self.resource_service_map[resource] = handle_obj.service_type
|
||||||
setattr(self, 'list_%ss' % resource,
|
for operation, index in six.iteritems(
|
||||||
functools.partial(self.list_resources, resource))
|
resource_handle.operation_index_map):
|
||||||
|
# add parentheses to emphasize we mean to do bitwise and
|
||||||
|
if (handle_obj.support_resource[resource] & index) == 0:
|
||||||
|
continue
|
||||||
|
self.operation_resources_map[operation].add(resource)
|
||||||
|
setattr(self, '%s_%ss' % (operation, resource),
|
||||||
|
functools.partial(
|
||||||
|
getattr(self, '%s_resources' % operation),
|
||||||
|
resource))
|
||||||
|
|
||||||
def _get_admin_token(self):
|
def _get_admin_token(self):
|
||||||
auth = auth_identity.Password(
|
auth = auth_identity.Password(
|
||||||
|
@ -233,6 +268,7 @@ class Client(object):
|
||||||
"""
|
"""
|
||||||
self._update_endpoint_from_keystone(cxt, False)
|
self._update_endpoint_from_keystone(cxt, False)
|
||||||
|
|
||||||
|
@_safe_operation('list')
|
||||||
def list_resources(self, resource, cxt, filters=None):
|
def list_resources(self, resource, cxt, filters=None):
|
||||||
"""Query resource in site of top layer
|
"""Query resource in site of top layer
|
||||||
|
|
||||||
|
@ -250,19 +286,49 @@ class Client(object):
|
||||||
:return: list of dict containing resources information
|
:return: list of dict containing resources information
|
||||||
:raises: EndpointNotAvailable
|
:raises: EndpointNotAvailable
|
||||||
"""
|
"""
|
||||||
if resource not in self.resource_service_map:
|
|
||||||
raise exception.ResourceNotSupported(resource, 'list')
|
|
||||||
service = self.resource_service_map[resource]
|
service = self.resource_service_map[resource]
|
||||||
self._ensure_endpoint_set(cxt, service)
|
|
||||||
handle = self.service_handle_map[service]
|
handle = self.service_handle_map[service]
|
||||||
filters = filters or []
|
filters = filters or []
|
||||||
try:
|
return handle.handle_list(cxt, resource, filters)
|
||||||
return handle.handle_list(cxt, resource, filters)
|
|
||||||
except exception.EndpointNotAvailable as e:
|
@_safe_operation('create')
|
||||||
if cfg.CONF.client.auto_refresh_endpoint:
|
def create_resources(self, resource, cxt, *args, **kwargs):
|
||||||
LOG.warn(e.message + ', update endpoint and try again')
|
"""Create resource in site of top layer
|
||||||
self._update_endpoint_from_keystone(cxt, True)
|
|
||||||
self._ensure_endpoint_set(cxt, service)
|
Directly invoke this method to create resources, or use
|
||||||
return handle.handle_list(cxt, resource, filters)
|
create_(resource)s (self, cxt, *args, **kwargs). These methods are
|
||||||
else:
|
automatically generated according to the supported resources of each
|
||||||
raise e
|
ResourceHandle class.
|
||||||
|
|
||||||
|
:param resource: resource type
|
||||||
|
:param cxt: context object
|
||||||
|
:param args, kwargs: passed according to resource type
|
||||||
|
--------------------------
|
||||||
|
resource -> args -> kwargs
|
||||||
|
--------------------------
|
||||||
|
aggregate -> name, availability_zone_name -> none
|
||||||
|
--------------------------
|
||||||
|
:return: a dict containing resource information
|
||||||
|
:raises: EndpointNotAvailable
|
||||||
|
"""
|
||||||
|
service = self.resource_service_map[resource]
|
||||||
|
handle = self.service_handle_map[service]
|
||||||
|
return handle.handle_create(cxt, resource, *args, **kwargs)
|
||||||
|
|
||||||
|
@_safe_operation('delete')
|
||||||
|
def delete_resources(self, resource, cxt, resource_id):
|
||||||
|
"""Delete resource in site of top layer
|
||||||
|
|
||||||
|
Directly invoke this method to delete resources, or use
|
||||||
|
delete_(resource)s (self, cxt, obj_id). These methods are
|
||||||
|
automatically generated according to the supported resources
|
||||||
|
of each ResourceHandle class.
|
||||||
|
:param resource: resource type
|
||||||
|
:param cxt: context object
|
||||||
|
:param resource_id: id of resource
|
||||||
|
:return: None
|
||||||
|
:raises: EndpointNotAvailable
|
||||||
|
"""
|
||||||
|
service = self.resource_service_map[resource]
|
||||||
|
handle = self.service_handle_map[service]
|
||||||
|
handle.handle_delete(cxt, resource, resource_id)
|
||||||
|
|
|
@ -19,7 +19,9 @@ import glanceclient.exc as g_exceptions
|
||||||
from neutronclient.common import exceptions as q_exceptions
|
from neutronclient.common import exceptions as q_exceptions
|
||||||
from neutronclient.neutron import client as q_client
|
from neutronclient.neutron import client as q_client
|
||||||
from novaclient import client as n_client
|
from novaclient import client as n_client
|
||||||
|
from novaclient import exceptions as n_exceptions
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
from requests import exceptions as r_exceptions
|
from requests import exceptions as r_exceptions
|
||||||
|
|
||||||
from tricircle.db import exception as exception
|
from tricircle.db import exception as exception
|
||||||
|
@ -38,6 +40,12 @@ client_opts = [
|
||||||
cfg.CONF.register_opts(client_opts, group='client')
|
cfg.CONF.register_opts(client_opts, group='client')
|
||||||
|
|
||||||
|
|
||||||
|
LIST, CREATE, DELETE = 1, 2, 4
|
||||||
|
operation_index_map = {'list': LIST, 'create': CREATE, 'delete': DELETE}
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _transform_filters(filters):
|
def _transform_filters(filters):
|
||||||
filter_dict = {}
|
filter_dict = {}
|
||||||
for query_filter in filters:
|
for query_filter in filters:
|
||||||
|
@ -64,7 +72,7 @@ class ResourceHandle(object):
|
||||||
|
|
||||||
class GlanceResourceHandle(ResourceHandle):
|
class GlanceResourceHandle(ResourceHandle):
|
||||||
service_type = 'glance'
|
service_type = 'glance'
|
||||||
support_resource = ('image', )
|
support_resource = {'image': LIST}
|
||||||
|
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
return g_client.Client('1',
|
return g_client.Client('1',
|
||||||
|
@ -74,8 +82,6 @@ class GlanceResourceHandle(ResourceHandle):
|
||||||
timeout=cfg.CONF.client.glance_timeout)
|
timeout=cfg.CONF.client.glance_timeout)
|
||||||
|
|
||||||
def handle_list(self, cxt, resource, filters):
|
def handle_list(self, cxt, resource, filters):
|
||||||
if resource not in self.support_resource:
|
|
||||||
return []
|
|
||||||
try:
|
try:
|
||||||
client = self._get_client(cxt)
|
client = self._get_client(cxt)
|
||||||
collection = '%ss' % resource
|
collection = '%ss' % resource
|
||||||
|
@ -89,8 +95,12 @@ class GlanceResourceHandle(ResourceHandle):
|
||||||
|
|
||||||
class NeutronResourceHandle(ResourceHandle):
|
class NeutronResourceHandle(ResourceHandle):
|
||||||
service_type = 'neutron'
|
service_type = 'neutron'
|
||||||
support_resource = ('network', 'subnet', 'port', 'router',
|
support_resource = {'network': LIST,
|
||||||
'security_group', 'security_group_rule')
|
'subnet': LIST,
|
||||||
|
'port': LIST,
|
||||||
|
'router': LIST,
|
||||||
|
'security_group': LIST,
|
||||||
|
'security_group_rule': LIST}
|
||||||
|
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
return q_client.Client('2.0',
|
return q_client.Client('2.0',
|
||||||
|
@ -100,8 +110,6 @@ class NeutronResourceHandle(ResourceHandle):
|
||||||
timeout=cfg.CONF.client.neutron_timeout)
|
timeout=cfg.CONF.client.neutron_timeout)
|
||||||
|
|
||||||
def handle_list(self, cxt, resource, filters):
|
def handle_list(self, cxt, resource, filters):
|
||||||
if resource not in self.support_resource:
|
|
||||||
return []
|
|
||||||
try:
|
try:
|
||||||
client = self._get_client(cxt)
|
client = self._get_client(cxt)
|
||||||
collection = '%ss' % resource
|
collection = '%ss' % resource
|
||||||
|
@ -116,7 +124,9 @@ class NeutronResourceHandle(ResourceHandle):
|
||||||
|
|
||||||
class NovaResourceHandle(ResourceHandle):
|
class NovaResourceHandle(ResourceHandle):
|
||||||
service_type = 'nova'
|
service_type = 'nova'
|
||||||
support_resource = ('flavor', 'server')
|
support_resource = {'flavor': LIST,
|
||||||
|
'server': LIST,
|
||||||
|
'aggregate': LIST | CREATE | DELETE}
|
||||||
|
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
cli = n_client.Client('2',
|
cli = n_client.Client('2',
|
||||||
|
@ -128,8 +138,6 @@ class NovaResourceHandle(ResourceHandle):
|
||||||
return cli
|
return cli
|
||||||
|
|
||||||
def handle_list(self, cxt, resource, filters):
|
def handle_list(self, cxt, resource, filters):
|
||||||
if resource not in self.support_resource:
|
|
||||||
return []
|
|
||||||
try:
|
try:
|
||||||
client = self._get_client(cxt)
|
client = self._get_client(cxt)
|
||||||
collection = '%ss' % resource
|
collection = '%ss' % resource
|
||||||
|
@ -145,3 +153,27 @@ class NovaResourceHandle(ResourceHandle):
|
||||||
self.endpoint_url = None
|
self.endpoint_url = None
|
||||||
raise exception.EndpointNotAvailable('nova',
|
raise exception.EndpointNotAvailable('nova',
|
||||||
client.client.management_url)
|
client.client.management_url)
|
||||||
|
|
||||||
|
def handle_create(self, cxt, resource, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
client = self._get_client(cxt)
|
||||||
|
collection = '%ss' % resource
|
||||||
|
return getattr(client, collection).create(
|
||||||
|
*args, **kwargs).to_dict()
|
||||||
|
except r_exceptions.ConnectTimeout:
|
||||||
|
self.endpoint_url = None
|
||||||
|
raise exception.EndpointNotAvailable('nova',
|
||||||
|
client.client.management_url)
|
||||||
|
|
||||||
|
def handle_delete(self, cxt, resource, resource_id):
|
||||||
|
try:
|
||||||
|
client = self._get_client(cxt)
|
||||||
|
collection = '%ss' % resource
|
||||||
|
return getattr(client, collection).delete(resource_id)
|
||||||
|
except r_exceptions.ConnectTimeout:
|
||||||
|
self.endpoint_url = None
|
||||||
|
raise exception.EndpointNotAvailable('nova',
|
||||||
|
client.client.management_url)
|
||||||
|
except n_exceptions.NotFound:
|
||||||
|
LOG.debug("Delete %(resource)s %(resource_id)s which not found",
|
||||||
|
{'resource': resource, 'resource_id': resource_id})
|
||||||
|
|
|
@ -35,6 +35,7 @@ FAKE_SERVICE_NAME = 'fake_service_name'
|
||||||
FAKE_TYPE = 'fake_type'
|
FAKE_TYPE = 'fake_type'
|
||||||
FAKE_URL = 'http://127.0.0.1:12345'
|
FAKE_URL = 'http://127.0.0.1:12345'
|
||||||
FAKE_URL_INVALID = 'http://127.0.0.1:23456'
|
FAKE_URL_INVALID = 'http://127.0.0.1:23456'
|
||||||
|
FAKE_RESOURCES = [{'name': 'res1'}, {'name': 'res2'}]
|
||||||
|
|
||||||
|
|
||||||
class FakeException(Exception):
|
class FakeException(Exception):
|
||||||
|
@ -44,18 +45,31 @@ class FakeException(Exception):
|
||||||
class FakeClient(object):
|
class FakeClient(object):
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
self.endpoint = url
|
self.endpoint = url
|
||||||
self.resources = [{'name': 'res1'}, {'name': 'res2'}]
|
|
||||||
|
|
||||||
def list_fake_res(self, search_opts):
|
def list_fake_res(self, search_opts):
|
||||||
# make sure endpoint is correctly set
|
# make sure endpoint is correctly set
|
||||||
if self.endpoint != FAKE_URL:
|
if self.endpoint != FAKE_URL:
|
||||||
raise FakeException()
|
raise FakeException()
|
||||||
if not search_opts:
|
if not search_opts:
|
||||||
return [res for res in self.resources]
|
return [res for res in FAKE_RESOURCES]
|
||||||
else:
|
else:
|
||||||
return [res for res in self.resources if (
|
return [res for res in FAKE_RESOURCES if (
|
||||||
res['name'] == search_opts['name'])]
|
res['name'] == search_opts['name'])]
|
||||||
|
|
||||||
|
def create_fake_res(self, name):
|
||||||
|
if self.endpoint != FAKE_URL:
|
||||||
|
raise FakeException()
|
||||||
|
FAKE_RESOURCES.append({'name': name})
|
||||||
|
return {'name': name}
|
||||||
|
|
||||||
|
def delete_fake_res(self, name):
|
||||||
|
if self.endpoint != FAKE_URL:
|
||||||
|
raise FakeException()
|
||||||
|
try:
|
||||||
|
FAKE_RESOURCES.remove({'name': name})
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FakeResHandle(resource_handle.ResourceHandle):
|
class FakeResHandle(resource_handle.ResourceHandle):
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
|
@ -70,6 +84,22 @@ class FakeResHandle(resource_handle.ResourceHandle):
|
||||||
self.endpoint_url = None
|
self.endpoint_url = None
|
||||||
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
||||||
|
|
||||||
|
def handle_create(self, cxt, resource, name):
|
||||||
|
try:
|
||||||
|
cli = self._get_client(cxt)
|
||||||
|
return cli.create_fake_res(name)
|
||||||
|
except FakeException:
|
||||||
|
self.endpoint_url = None
|
||||||
|
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
||||||
|
|
||||||
|
def handle_delete(self, cxt, resource, name):
|
||||||
|
try:
|
||||||
|
cli = self._get_client(cxt)
|
||||||
|
cli.delete_fake_res(name)
|
||||||
|
except FakeException:
|
||||||
|
self.endpoint_url = None
|
||||||
|
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
||||||
|
|
||||||
|
|
||||||
class ClientTest(unittest.TestCase):
|
class ClientTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -97,10 +127,16 @@ class ClientTest(unittest.TestCase):
|
||||||
models.create_service_type(self.context, type_dict)
|
models.create_service_type(self.context, type_dict)
|
||||||
models.create_site_service_configuration(self.context, config_dict)
|
models.create_site_service_configuration(self.context, config_dict)
|
||||||
|
|
||||||
|
global FAKE_RESOURCES
|
||||||
|
FAKE_RESOURCES = [{'name': 'res1'}, {'name': 'res2'}]
|
||||||
|
|
||||||
cfg.CONF.set_override(name='top_site_name', override=FAKE_SITE_NAME,
|
cfg.CONF.set_override(name='top_site_name', override=FAKE_SITE_NAME,
|
||||||
group='client')
|
group='client')
|
||||||
self.client = client.Client()
|
self.client = client.Client()
|
||||||
self.client.resource_service_map[FAKE_RESOURCE] = FAKE_TYPE
|
self.client.resource_service_map[FAKE_RESOURCE] = FAKE_TYPE
|
||||||
|
self.client.operation_resources_map['list'].add(FAKE_RESOURCE)
|
||||||
|
self.client.operation_resources_map['create'].add(FAKE_RESOURCE)
|
||||||
|
self.client.operation_resources_map['delete'].add(FAKE_RESOURCE)
|
||||||
self.client.service_handle_map[FAKE_TYPE] = FakeResHandle(None)
|
self.client.service_handle_map[FAKE_TYPE] = FakeResHandle(None)
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
|
@ -115,6 +151,19 @@ class ClientTest(unittest.TestCase):
|
||||||
'value': 'res2'}])
|
'value': 'res2'}])
|
||||||
self.assertEqual(resources, [{'name': 'res2'}])
|
self.assertEqual(resources, [{'name': 'res2'}])
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
resource = self.client.create_resources(FAKE_RESOURCE, self.context,
|
||||||
|
'res3')
|
||||||
|
self.assertEqual(resource, {'name': 'res3'})
|
||||||
|
resources = self.client.list_resources(FAKE_RESOURCE, self.context)
|
||||||
|
self.assertEqual(resources, [{'name': 'res1'}, {'name': 'res2'},
|
||||||
|
{'name': 'res3'}])
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
self.client.delete_resources(FAKE_RESOURCE, self.context, 'res1')
|
||||||
|
resources = self.client.list_resources(FAKE_RESOURCE, self.context)
|
||||||
|
self.assertEqual(resources, [{'name': 'res2'}])
|
||||||
|
|
||||||
def test_list_endpoint_not_found(self):
|
def test_list_endpoint_not_found(self):
|
||||||
cfg.CONF.set_override(name='auto_refresh_endpoint', override=False,
|
cfg.CONF.set_override(name='auto_refresh_endpoint', override=False,
|
||||||
group='client')
|
group='client')
|
||||||
|
@ -125,6 +174,18 @@ class ClientTest(unittest.TestCase):
|
||||||
self.client.list_resources,
|
self.client.list_resources,
|
||||||
FAKE_RESOURCE, self.context, [])
|
FAKE_RESOURCE, self.context, [])
|
||||||
|
|
||||||
|
def test_resource_not_supported(self):
|
||||||
|
# no such resource
|
||||||
|
self.assertRaises(exception.ResourceNotSupported,
|
||||||
|
self.client.list_resources,
|
||||||
|
'no_such_resource', self.context, [])
|
||||||
|
# remove "create" entry for FAKE_RESOURCE
|
||||||
|
self.client.operation_resources_map['create'].remove(FAKE_RESOURCE)
|
||||||
|
# operation not supported
|
||||||
|
self.assertRaises(exception.ResourceNotSupported,
|
||||||
|
self.client.create_resources,
|
||||||
|
FAKE_RESOURCE, self.context, [])
|
||||||
|
|
||||||
def test_list_endpoint_not_found_retry(self):
|
def test_list_endpoint_not_found_retry(self):
|
||||||
cfg.CONF.set_override(name='auto_refresh_endpoint', override=True,
|
cfg.CONF.set_override(name='auto_refresh_endpoint', override=True,
|
||||||
group='client')
|
group='client')
|
||||||
|
|
Loading…
Reference in New Issue