Merge "Extending stores-detail API"

This commit is contained in:
Zuul 2022-08-24 12:05:46 +00:00 committed by Gerrit Code Review
commit 5c675bddcf
6 changed files with 210 additions and 23 deletions

View File

@ -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
}
} }
] ]
} }

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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.