Browse Source

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
changes/04/225404/4
zhiyuan_cai 7 years ago
parent
commit
3d9d2daf3b
  1. 96
      tricircle/db/client.py
  2. 52
      tricircle/db/resource_handle.py
  3. 67
      tricircle/tests/unit/db/test_client.py

96
tricircle/db/client.py

@ -13,8 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import functools
import inspect
import six
import uuid
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__)
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):
def __init__(self):
self.auth_url = cfg.CONF.client.auth_url
self.resource_service_map = {}
self.operation_resources_map = collections.defaultdict(set)
self.service_handle_map = {}
for _, handle_class in inspect.getmembers(resource_handle):
if not inspect.isclass(handle_class):
@ -81,8 +108,16 @@ class Client(object):
self.service_handle_map[handle_obj.service_type] = handle_obj
for resource in handle_obj.support_resource:
self.resource_service_map[resource] = handle_obj.service_type
setattr(self, 'list_%ss' % resource,
functools.partial(self.list_resources, resource))
for operation, index in six.iteritems(
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):
auth = auth_identity.Password(
@ -233,6 +268,7 @@ class Client(object):
"""
self._update_endpoint_from_keystone(cxt, False)
@_safe_operation('list')
def list_resources(self, resource, cxt, filters=None):
"""Query resource in site of top layer
@ -250,19 +286,49 @@ class Client(object):
:return: list of dict containing resources information
:raises: EndpointNotAvailable
"""
if resource not in self.resource_service_map:
raise exception.ResourceNotSupported(resource, 'list')
service = self.resource_service_map[resource]
self._ensure_endpoint_set(cxt, service)
handle = self.service_handle_map[service]
filters = filters or []
try:
return handle.handle_list(cxt, resource, filters)
except exception.EndpointNotAvailable as e:
if cfg.CONF.client.auto_refresh_endpoint:
LOG.warn(e.message + ', update endpoint and try again')
self._update_endpoint_from_keystone(cxt, True)
self._ensure_endpoint_set(cxt, service)
return handle.handle_list(cxt, resource, filters)
else:
raise e
return handle.handle_list(cxt, resource, filters)
@_safe_operation('create')
def create_resources(self, resource, cxt, *args, **kwargs):
"""Create resource in site of top layer
Directly invoke this method to create resources, or use
create_(resource)s (self, cxt, *args, **kwargs). These methods are
automatically generated according to the supported resources of each
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)

52
tricircle/db/resource_handle.py

@ -19,7 +19,9 @@ import glanceclient.exc as g_exceptions
from neutronclient.common import exceptions as q_exceptions
from neutronclient.neutron import client as q_client
from novaclient import client as n_client
from novaclient import exceptions as n_exceptions
from oslo_config import cfg
from oslo_log import log as logging
from requests import exceptions as r_exceptions
from tricircle.db import exception as exception
@ -38,6 +40,12 @@ client_opts = [
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):
filter_dict = {}
for query_filter in filters:
@ -64,7 +72,7 @@ class ResourceHandle(object):
class GlanceResourceHandle(ResourceHandle):
service_type = 'glance'
support_resource = ('image', )
support_resource = {'image': LIST}
def _get_client(self, cxt):
return g_client.Client('1',
@ -74,8 +82,6 @@ class GlanceResourceHandle(ResourceHandle):
timeout=cfg.CONF.client.glance_timeout)
def handle_list(self, cxt, resource, filters):
if resource not in self.support_resource:
return []
try:
client = self._get_client(cxt)
collection = '%ss' % resource
@ -89,8 +95,12 @@ class GlanceResourceHandle(ResourceHandle):
class NeutronResourceHandle(ResourceHandle):
service_type = 'neutron'
support_resource = ('network', 'subnet', 'port', 'router',
'security_group', 'security_group_rule')
support_resource = {'network': LIST,
'subnet': LIST,
'port': LIST,
'router': LIST,
'security_group': LIST,
'security_group_rule': LIST}
def _get_client(self, cxt):
return q_client.Client('2.0',
@ -100,8 +110,6 @@ class NeutronResourceHandle(ResourceHandle):
timeout=cfg.CONF.client.neutron_timeout)
def handle_list(self, cxt, resource, filters):
if resource not in self.support_resource:
return []
try:
client = self._get_client(cxt)
collection = '%ss' % resource
@ -116,7 +124,9 @@ class NeutronResourceHandle(ResourceHandle):
class NovaResourceHandle(ResourceHandle):
service_type = 'nova'
support_resource = ('flavor', 'server')
support_resource = {'flavor': LIST,
'server': LIST,
'aggregate': LIST | CREATE | DELETE}
def _get_client(self, cxt):
cli = n_client.Client('2',
@ -128,8 +138,6 @@ class NovaResourceHandle(ResourceHandle):
return cli
def handle_list(self, cxt, resource, filters):
if resource not in self.support_resource:
return []
try:
client = self._get_client(cxt)
collection = '%ss' % resource
@ -145,3 +153,27 @@ class NovaResourceHandle(ResourceHandle):
self.endpoint_url = None
raise exception.EndpointNotAvailable('nova',
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})

67
tricircle/tests/unit/db/test_client.py

@ -35,6 +35,7 @@ FAKE_SERVICE_NAME = 'fake_service_name'
FAKE_TYPE = 'fake_type'
FAKE_URL = 'http://127.0.0.1:12345'
FAKE_URL_INVALID = 'http://127.0.0.1:23456'
FAKE_RESOURCES = [{'name': 'res1'}, {'name': 'res2'}]
class FakeException(Exception):
@ -44,18 +45,31 @@ class FakeException(Exception):
class FakeClient(object):
def __init__(self, url):
self.endpoint = url
self.resources = [{'name': 'res1'}, {'name': 'res2'}]
def list_fake_res(self, search_opts):
# make sure endpoint is correctly set
if self.endpoint != FAKE_URL:
raise FakeException()
if not search_opts:
return [res for res in self.resources]
return [res for res in FAKE_RESOURCES]
else:
return [res for res in self.resources if (
return [res for res in FAKE_RESOURCES if (
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):
def _get_client(self, cxt):
@ -70,6 +84,22 @@ class FakeResHandle(resource_handle.ResourceHandle):
self.endpoint_url = None
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):
def setUp(self):
@ -97,10 +127,16 @@ class ClientTest(unittest.TestCase):
models.create_service_type(self.context, type_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,
group='client')
self.client = client.Client()
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)
def test_list(self):
@ -115,6 +151,19 @@ class ClientTest(unittest.TestCase):
'value': '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):
cfg.CONF.set_override(name='auto_refresh_endpoint', override=False,
group='client')
@ -125,6 +174,18 @@ class ClientTest(unittest.TestCase):
self.client.list_resources,
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):
cfg.CONF.set_override(name='auto_refresh_endpoint', override=True,
group='client')

Loading…
Cancel
Save