diff --git a/test-requirements.txt b/test-requirements.txt index 9ab0467..11fa4e8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking<0.11,>=0.10.2 +hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD diff --git a/trio2o/cinder_apigw/controllers/volume.py b/trio2o/cinder_apigw/controllers/volume.py index 670005a..5451c88 100644 --- a/trio2o/cinder_apigw/controllers/volume.py +++ b/trio2o/cinder_apigw/controllers/volume.py @@ -157,7 +157,7 @@ class VolumeController(rest.RestController): context = t_context.extract_context_from_environ() if _id == 'detail': - return {'volumes': self._get_all(context)} + return self._get_all(context) # TODO(joehuang): get the release of top and bottom t_release = cons.R_MITAKA @@ -218,12 +218,14 @@ class VolumeController(rest.RestController): # now combined with 'detail' context = t_context.extract_context_from_environ() - return {'volumes': self._get_all(context)} + return self._get_all(context) def _get_all(self, context): # TODO(joehuang): query optimization for pagination, sort, etc - ret = [] + ret = {} + ret['volumes'] = [] + pods = az_ag.list_pods_by_tenant(context, self.tenant_id) for pod in pods: if pod['pod_name'] == '': @@ -278,7 +280,10 @@ class VolumeController(rest.RestController): vol['availability_zone'] = pod['az_name'] - ret.extend(b_ret_body['volumes']) + ret['volumes'].extend(b_ret_body['volumes']) + + if b_ret_body.get('volumes_links'): + ret['volumes_links'] = b_ret_body['volumes_links'] return ret @expose(generic=True, template='json') diff --git a/trio2o/common/client.py b/trio2o/common/client.py index 648b1b4..eafb58e 100644 --- a/trio2o/common/client.py +++ b/trio2o/common/client.py @@ -229,8 +229,6 @@ class Client(object): def _get_config_with_retry(self, cxt, filters, pod, service, retry): conf_list = api.list_pod_service_configurations(cxt, filters) - if len(conf_list) > 1: - raise exceptions.EndpointNotUnique(pod, service) if len(conf_list) == 0: if not retry: raise exceptions.EndpointNotFound(pod, service) @@ -295,7 +293,7 @@ class Client(object): cxt, config_filters) if len(config_list) > 1: - raise exceptions.EndpointNotUnique(pod_id, service) + continue if len(config_list) == 1: config_id = config_list[0]['service_id'] update_dict = { diff --git a/trio2o/common/resource_handle.py b/trio2o/common/resource_handle.py index 299b781..478c15d 100644 --- a/trio2o/common/resource_handle.py +++ b/trio2o/common/resource_handle.py @@ -84,7 +84,7 @@ class GlanceResourceHandle(ResourceHandle): support_resource = {'image': LIST | GET} def _get_client(self, cxt): - return g_client.Client('1', + return g_client.Client('2', token=cxt.auth_token, auth_url=self.auth_url, endpoint=self.endpoint_url, @@ -105,7 +105,7 @@ class GlanceResourceHandle(ResourceHandle): try: client = self._get_client(cxt) collection = '%ss' % resource - return getattr(client, collection).get(resource_id).to_dict() + return getattr(client, collection).get(resource_id) except g_exceptions.InvalidEndpoint: self.endpoint_url = None raise exceptions.EndpointNotAvailable('glance', diff --git a/trio2o/nova_apigw/controllers/image.py b/trio2o/nova_apigw/controllers/image.py index bc8e6e3..1cea02d 100644 --- a/trio2o/nova_apigw/controllers/image.py +++ b/trio2o/nova_apigw/controllers/image.py @@ -23,6 +23,16 @@ from trio2o.common.i18n import _ from trio2o.common import utils import trio2o.db.api as db_api +SUPPORTED_FILTERS = { + 'name': 'name', + 'status': 'status', + 'changes-since': 'changes-since', + 'server': 'property-instance_uuid', + 'type': 'property-image_type', + 'minRam': 'min_ram', + 'minDisk': 'min_disk', +} + class ImageController(rest.RestController): @@ -90,27 +100,58 @@ class ImageController(rest.RestController): 'minRam': int(image.get('min_ram') or 0), 'minDisk': int(image.get('min_disk') or 0), 'metadata': image.get('properties', {}), - 'created': self._format_date(image.get('created_at')), - 'updated': self._format_date(image.get('updated_at')), + 'created': image.get('created_at'), + 'updated': image.get('updated_at'), 'status': self._get_status(image), 'progress': self._get_progress(image), 'links': self._get_links(context, image) } @expose(generic=True, template='json') - def get_one(self, _id): + def get_one(self, _id, **kwargs): context = t_context.extract_context_from_environ() if _id == 'detail': - return self.get_all() + return self.get_all(**kwargs) image = self.client.get_images(context, _id) if not image: return utils.format_nova_error(404, _('Image not found')) return {'image': self._construct_show_image_entry(context, image)} @expose(generic=True, template='json') - def get_all(self): + def get_all(self, **kwargs): context = t_context.extract_context_from_environ() - images = self.client.list_images(context) + filters = self._get_filters(kwargs) + filters = [{'key': key, + 'comparator': 'eq', + 'value': value} for key, value in filters.iteritems()] + images = self.client.list_images(context, filters=filters) ret_images = [self._construct_list_image_entry( context, image) for image in images] return {'images': ret_images} + + def _get_filters(self, params): + """Return a dictionary of query param filters from the request. + + :param params: the URI params coming from the wsgi layer + :return a dict of key/value filters + """ + filters = {} + for param in params: + if param in SUPPORTED_FILTERS or param.startswith('property-'): + # map filter name or carry through if property-* + filter_name = SUPPORTED_FILTERS.get(param, param) + filters[filter_name] = params.get(param) + + # ensure server filter is the instance uuid + filter_name = 'property-instance_uuid' + try: + filters[filter_name] = filters[filter_name].rsplit('/', 1)[1] + except (AttributeError, IndexError, KeyError): + pass + + filter_name = 'status' + if filter_name in filters: + # The Image API expects us to use lowercase strings for status + filters[filter_name] = filters[filter_name].lower() + + return filters diff --git a/trio2o/tests/unit/common/test_client.py b/trio2o/tests/unit/common/test_client.py index 736fc64..819dd1a 100644 --- a/trio2o/tests/unit/common/test_client.py +++ b/trio2o/tests/unit/common/test_client.py @@ -223,19 +223,6 @@ class ClientTest(unittest.TestCase): FAKE_RESOURCE, self.context, []) self.assertEqual(resources, [{'name': 'res1'}, {'name': 'res2'}]) - def test_list_endpoint_not_unique(self): - # add a new configuration with same pod and service type - config_dict = { - 'service_id': FAKE_SERVICE_ID + '_new', - 'pod_id': FAKE_SITE_ID, - 'service_type': FAKE_TYPE, - 'service_url': FAKE_URL - } - api.create_pod_service_configuration(self.context, config_dict) - self.assertRaises(exceptions.EndpointNotUnique, - self.client.list_resources, - FAKE_RESOURCE, self.context, []) - def test_list_endpoint_not_valid(self): cfg.CONF.set_override(name='auto_refresh_endpoint', override=False, group='client')