Merge "Extending stores-detail API"
This commit is contained in:
commit
5c675bddcf
@ -6,14 +6,41 @@
|
|||||||
"description": "More expensive store with data redundancy",
|
"description": "More expensive store with data redundancy",
|
||||||
"default": true,
|
"default": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"pool": "pool1"
|
"pool": "pool1",
|
||||||
|
"chunk_size": 65536,
|
||||||
|
"thin_provisioning": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id":"cheap",
|
"id":"cheap",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"description": "Less expensive store for seldom-used images",
|
"description": "Less expensive store for seldom-used images",
|
||||||
"properties": {}
|
"properties": {
|
||||||
|
"datadir": "fdir",
|
||||||
|
"chunk_size": 65536,
|
||||||
|
"thin_provisioning": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":"fast",
|
||||||
|
"type": "cinder",
|
||||||
|
"description": "Reasonably-priced fast store",
|
||||||
|
"properties": {
|
||||||
|
"volume_type": "volume1",
|
||||||
|
"use_multipath": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":"slow",
|
||||||
|
"type": "swift",
|
||||||
|
"description": "Entry-level store balancing price and speed",
|
||||||
|
"properties": {
|
||||||
|
"container": "container1",
|
||||||
|
"large_object_size": 52428,
|
||||||
|
"large_object_chunk_size": 204800
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,54 @@ class InfoController(object):
|
|||||||
|
|
||||||
return {'stores': backends}
|
return {'stores': backends}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_rbd_properties(store_detail):
|
||||||
|
return {
|
||||||
|
'chunk_size': store_detail.chunk_size,
|
||||||
|
'pool': store_detail.pool,
|
||||||
|
'thin_provisioning': store_detail.thin_provisioning
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_file_properties(store_detail):
|
||||||
|
return {
|
||||||
|
'data_dir': store_detail.datadir,
|
||||||
|
'chunk_size': store_detail.chunk_size,
|
||||||
|
'thin_provisioning': store_detail.thin_provisioning
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_cinder_properties(store_detail):
|
||||||
|
return {
|
||||||
|
'volume_type': store_detail.store_conf.cinder_volume_type,
|
||||||
|
'use_multipath': store_detail.store_conf.cinder_use_multipath
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_swift_properties(store_detail):
|
||||||
|
return {
|
||||||
|
'container': store_detail.container,
|
||||||
|
'large_object_size': store_detail.large_object_size,
|
||||||
|
'large_object_chunk_size': store_detail.large_object_chunk_size
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_s3_properties(store_detail):
|
||||||
|
return {
|
||||||
|
's3_store_large_object_size':
|
||||||
|
store_detail.s3_store_large_object_size,
|
||||||
|
's3_store_large_object_chunk_size':
|
||||||
|
store_detail.s3_store_large_object_chunk_size,
|
||||||
|
's3_store_thread_pools':
|
||||||
|
store_detail.s3_store_thread_pools
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_http_properties(store_detail):
|
||||||
|
# NOTE(mrjoshi): Thre are no useful properties
|
||||||
|
# to be exposed.
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_stores_detail(self, req):
|
def get_stores_detail(self, req):
|
||||||
enabled_backends = CONF.enabled_backends
|
enabled_backends = CONF.enabled_backends
|
||||||
stores = self.get_stores(req).get('stores')
|
stores = self.get_stores(req).get('stores')
|
||||||
@ -84,17 +132,24 @@ class InfoController(object):
|
|||||||
api_policy.DiscoveryAPIPolicy(
|
api_policy.DiscoveryAPIPolicy(
|
||||||
req.context,
|
req.context,
|
||||||
enforcer=self.policy).stores_info_detail()
|
enforcer=self.policy).stores_info_detail()
|
||||||
|
|
||||||
|
store_mapper = {
|
||||||
|
'rbd': self._get_rbd_properties,
|
||||||
|
'file': self._get_file_properties,
|
||||||
|
'cinder': self._get_cinder_properties,
|
||||||
|
'swift': self._get_swift_properties,
|
||||||
|
's3': self._get_s3_properties,
|
||||||
|
'http': self._get_http_properties
|
||||||
|
}
|
||||||
|
|
||||||
for store in stores:
|
for store in stores:
|
||||||
store['type'] = enabled_backends[store['id']]
|
store_type = enabled_backends[store['id']]
|
||||||
store['properties'] = {}
|
store['type'] = store_type
|
||||||
if store['type'] == 'rbd':
|
store_detail = g_store.get_store_from_store_identifier(
|
||||||
store_detail = g_store.get_store_from_store_identifier(
|
store['id'])
|
||||||
store['id'])
|
store['properties'] = store_mapper.get(store_type)(
|
||||||
store['properties'] = {'chunk_size':
|
store_detail)
|
||||||
store_detail.chunk_size,
|
|
||||||
'pool': store_detail.pool,
|
|
||||||
'thin_provisioning':
|
|
||||||
store_detail.thin_provisioning}
|
|
||||||
except exception.Forbidden as e:
|
except exception.Forbidden as e:
|
||||||
LOG.debug("User not permitted to view details")
|
LOG.debug("User not permitted to view details")
|
||||||
raise webob.exc.HTTPForbidden(explanation=e.msg)
|
raise webob.exc.HTTPForbidden(explanation=e.msg)
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
|
import http.client as http
|
||||||
|
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
|
||||||
@ -96,3 +97,68 @@ class TestDiscovery(functional.SynchronousAPIBase):
|
|||||||
expected['image_count_total']['usage'] = 1
|
expected['image_count_total']['usage'] = 1
|
||||||
expected['image_size_total']['usage'] = 1
|
expected['image_size_total']['usage'] = 1
|
||||||
self._assert_usage(expected)
|
self._assert_usage(expected)
|
||||||
|
|
||||||
|
def test_stores(self):
|
||||||
|
# NOTE(mrjoshi): As this is a functional test, we are
|
||||||
|
# testing the functionality with file stores.
|
||||||
|
|
||||||
|
self.start_server()
|
||||||
|
|
||||||
|
# If user is admin or non-admin the store list will be
|
||||||
|
# displayed.
|
||||||
|
stores = self.api_get('/v2/info/stores').json['stores']
|
||||||
|
expected = {
|
||||||
|
"stores": [
|
||||||
|
{
|
||||||
|
"id": "store1",
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "store2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "store3"
|
||||||
|
}]}
|
||||||
|
|
||||||
|
self.assertEqual(expected['stores'], stores)
|
||||||
|
|
||||||
|
# If user is admin the store list will be displayed
|
||||||
|
# along with store properties.
|
||||||
|
stores = self.api_get('/v2/info/stores/detail').json['stores']
|
||||||
|
expected = {
|
||||||
|
"stores": [
|
||||||
|
{
|
||||||
|
"id": "store1",
|
||||||
|
"default": "true",
|
||||||
|
"type": "file",
|
||||||
|
"properties": {
|
||||||
|
"data_dir": self._store_dir('store1'),
|
||||||
|
"chunk_size": 65536,
|
||||||
|
"thin_provisioning": False
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "store2",
|
||||||
|
"type": "file",
|
||||||
|
"properties": {
|
||||||
|
"data_dir": self._store_dir('store2'),
|
||||||
|
"chunk_size": 65536,
|
||||||
|
"thin_provisioning": False
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "store3",
|
||||||
|
"type": "file",
|
||||||
|
"properties": {
|
||||||
|
"data_dir": self._store_dir('store3'),
|
||||||
|
"chunk_size": 65536,
|
||||||
|
"thin_provisioning": False
|
||||||
|
}
|
||||||
|
}]}
|
||||||
|
|
||||||
|
self.assertEqual(expected['stores'], stores)
|
||||||
|
|
||||||
|
# If user is non-admin 403 Error response will be returned.
|
||||||
|
response = self.api_get('/v2/info/stores/detail',
|
||||||
|
headers={'X-Roles': 'member'})
|
||||||
|
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||||
|
@ -17,7 +17,9 @@ import os
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import glance_store as store
|
import glance_store as store
|
||||||
|
from glance_store._drivers import cinder
|
||||||
from glance_store._drivers import rbd as rbd_store
|
from glance_store._drivers import rbd as rbd_store
|
||||||
|
from glance_store._drivers import swift
|
||||||
from glance_store import location
|
from glance_store import location
|
||||||
from oslo_concurrency import lockutils
|
from oslo_concurrency import lockutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -74,21 +76,36 @@ class MultiStoreClearingUnitTest(test_utils.BaseTestCase):
|
|||||||
rbd_store.rados = mock.MagicMock()
|
rbd_store.rados = mock.MagicMock()
|
||||||
rbd_store.rbd = mock.MagicMock()
|
rbd_store.rbd = mock.MagicMock()
|
||||||
rbd_store.Store._set_url_prefix = mock.MagicMock()
|
rbd_store.Store._set_url_prefix = mock.MagicMock()
|
||||||
|
cinder.cinderclient = mock.MagicMock()
|
||||||
|
cinder.Store.get_cinderclient = mock.MagicMock()
|
||||||
|
swift.swiftclient = mock.MagicMock()
|
||||||
|
swift.BaseStore.get_store_connection = mock.MagicMock()
|
||||||
self.config(enabled_backends={'fast': 'file', 'cheap': 'file',
|
self.config(enabled_backends={'fast': 'file', 'cheap': 'file',
|
||||||
'readonly_store': 'http',
|
'readonly_store': 'http',
|
||||||
'fast-cinder': 'cinder',
|
'fast-cinder': 'cinder',
|
||||||
'fast-rbd': 'rbd'})
|
'fast-rbd': 'rbd', 'reliable': 'swift'})
|
||||||
store.register_store_opts(CONF)
|
store.register_store_opts(CONF)
|
||||||
|
|
||||||
self.config(default_backend='fast',
|
self.config(default_backend='fast',
|
||||||
group='glance_store')
|
group='glance_store')
|
||||||
|
|
||||||
self.config(filesystem_store_datadir=self.test_dir,
|
self.config(filesystem_store_datadir=self.test_dir,
|
||||||
|
filesystem_thin_provisioning=False,
|
||||||
|
filesystem_store_chunk_size=65536,
|
||||||
group='fast')
|
group='fast')
|
||||||
self.config(filesystem_store_datadir=self.test_dir2,
|
self.config(filesystem_store_datadir=self.test_dir2,
|
||||||
|
filesystem_thin_provisioning=False,
|
||||||
|
filesystem_store_chunk_size=65536,
|
||||||
group='cheap')
|
group='cheap')
|
||||||
self.config(rbd_store_chunk_size=8688388, rbd_store_pool='images',
|
self.config(rbd_store_chunk_size=8688388, rbd_store_pool='images',
|
||||||
rbd_thin_provisioning=False, group='fast-rbd')
|
rbd_thin_provisioning=False, group='fast-rbd')
|
||||||
|
self.config(cinder_volume_type='lvmdriver-1',
|
||||||
|
cinder_use_multipath=False, group='fast-cinder')
|
||||||
|
self.config(swift_store_container='glance',
|
||||||
|
swift_store_large_object_size=524288000,
|
||||||
|
swift_store_large_object_chunk_size=204800000,
|
||||||
|
group='reliable')
|
||||||
|
|
||||||
store.create_multi_stores(CONF)
|
store.create_multi_stores(CONF)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class TestInfoControllers(base.MultiStoreClearingUnitTest):
|
|||||||
|
|
||||||
def test_get_stores(self):
|
def test_get_stores(self):
|
||||||
available_stores = ['cheap', 'fast', 'readonly_store', 'fast-cinder',
|
available_stores = ['cheap', 'fast', 'readonly_store', 'fast-cinder',
|
||||||
'fast-rbd']
|
'fast-rbd', 'reliable']
|
||||||
req = unit_test_utils.get_fake_request()
|
req = unit_test_utils.get_fake_request()
|
||||||
output = self.controller.get_stores(req)
|
output = self.controller.get_stores(req)
|
||||||
self.assertIn('stores', output)
|
self.assertIn('stores', output)
|
||||||
@ -50,7 +50,7 @@ class TestInfoControllers(base.MultiStoreClearingUnitTest):
|
|||||||
|
|
||||||
def test_get_stores_read_only_store(self):
|
def test_get_stores_read_only_store(self):
|
||||||
available_stores = ['cheap', 'fast', 'readonly_store', 'fast-cinder',
|
available_stores = ['cheap', 'fast', 'readonly_store', 'fast-cinder',
|
||||||
'fast-rbd']
|
'fast-rbd', 'reliable']
|
||||||
req = unit_test_utils.get_fake_request()
|
req = unit_test_utils.get_fake_request()
|
||||||
output = self.controller.get_stores(req)
|
output = self.controller.get_stores(req)
|
||||||
self.assertIn('stores', output)
|
self.assertIn('stores', output)
|
||||||
@ -77,22 +77,36 @@ class TestInfoControllers(base.MultiStoreClearingUnitTest):
|
|||||||
|
|
||||||
def test_get_stores_detail(self):
|
def test_get_stores_detail(self):
|
||||||
available_stores = ['cheap', 'fast', 'readonly_store', 'fast-cinder',
|
available_stores = ['cheap', 'fast', 'readonly_store', 'fast-cinder',
|
||||||
'fast-rbd']
|
'fast-rbd', 'reliable']
|
||||||
available_store_type = ['file', 'file', 'http', 'cinder', 'rbd']
|
available_store_type = ['file', 'file', 'http', 'cinder', 'rbd',
|
||||||
|
'swift']
|
||||||
req = unit_test_utils.get_fake_request(roles=['admin'])
|
req = unit_test_utils.get_fake_request(roles=['admin'])
|
||||||
output = self.controller.get_stores_detail(req)
|
output = self.controller.get_stores_detail(req)
|
||||||
|
self.assertEqual(len(CONF.enabled_backends), len(output['stores']))
|
||||||
self.assertIn('stores', output)
|
self.assertIn('stores', output)
|
||||||
for stores in output['stores']:
|
for stores in output['stores']:
|
||||||
self.assertIn('id', stores)
|
self.assertIn('id', stores)
|
||||||
self.assertIn(stores['id'], available_stores)
|
self.assertIn(stores['id'], available_stores)
|
||||||
self.assertIn(stores['type'], available_store_type)
|
self.assertIn(stores['type'], available_store_type)
|
||||||
self.assertIsNotNone(stores['properties'])
|
self.assertIsNotNone(stores['properties'])
|
||||||
if stores['id'] == 'fast-rbd':
|
|
||||||
self.assertIn('chunk_size', stores['properties'])
|
def test_get_stores_detail_properties(self):
|
||||||
self.assertIn('pool', stores['properties'])
|
store_attributes = {'rbd': ['chunk_size', 'pool', 'thin_provisioning'],
|
||||||
self.assertIn('thin_provisioning', stores['properties'])
|
'file': ['data_dir', 'chunk_size',
|
||||||
else:
|
'thin_provisioning'],
|
||||||
self.assertEqual({}, stores['properties'])
|
'cinder': ['volume_type', 'use_multipath'],
|
||||||
|
'swift': ['container',
|
||||||
|
'large_object_size',
|
||||||
|
'large_object_chunk_size'],
|
||||||
|
'http': []}
|
||||||
|
req = unit_test_utils.get_fake_request(roles=['admin'])
|
||||||
|
output = self.controller.get_stores_detail(req)
|
||||||
|
self.assertEqual(len(CONF.enabled_backends), len(output['stores']))
|
||||||
|
self.assertIn('stores', output)
|
||||||
|
for store in output['stores']:
|
||||||
|
actual_attribute = list(store['properties'].keys())
|
||||||
|
expected_attribute = store_attributes[store['type']]
|
||||||
|
self.assertEqual(actual_attribute, expected_attribute)
|
||||||
|
|
||||||
def test_get_stores_detail_non_admin(self):
|
def test_get_stores_detail_non_admin(self):
|
||||||
req = unit_test_utils.get_fake_request()
|
req = unit_test_utils.get_fake_request()
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
This release brings expansion in the functionality of
|
||||||
|
stores-detail API. The stores detail API will list the
|
||||||
|
way each store is configured, whereas previously this
|
||||||
|
worked only for rbd store. The API remains admin-only
|
||||||
|
by default as it exposes backend information.
|
Loading…
Reference in New Issue
Block a user