objectstore/rgw: Add config option to support RGW implicit tenants

If "rgw keystone implicit tenants" is enabled for Rados Gateway to
enable Multi-Tenacy [1], the created RGW user IDs are in the format
"keystone_project_id$keystone_project_id", i.e. the RGW tenant ID is
added to the user ID. This breaks metering via Ceilometer as the usage
is always queried for uid=keystone_project_id. If implicit tenants are
enabled in RGW, Ceilometer has to query based on the adapted used IDs.
This commit introduces support for querying the correct user accounts.

[1] http://docs.ceph.com/docs/mimic/radosgw/multitenancy/

Change-Id: I6de4c5ce0e4f59c1d952f8fd39de64937e781280
Signed-off-by: Felix Walter <felix.walter@cloudandheat.com>
This commit is contained in:
Felix Walter 2018-06-11 15:12:42 +02:00
parent 8f068ebbd3
commit 246d01beb5
4 changed files with 55 additions and 5 deletions

View File

@ -40,6 +40,12 @@ CREDENTIAL_OPTS = [
help='Secret key for Radosgw Admin.')
]
CLIENT_OPTS = [
cfg.BoolOpt('implicit_tenants',
default=False,
help='Whether RGW uses implicit tenants or not.'),
]
class _Base(plugin_base.PollsterBase):
METHOD = 'bucket'
@ -49,6 +55,7 @@ class _Base(plugin_base.PollsterBase):
super(_Base, self).__init__(conf)
self.access_key = self.conf.rgw_admin_credentials.access_key
self.secret = self.conf.rgw_admin_credentials.secret_key
self.implicit_tenants = self.conf.rgw_client.implicit_tenants
@property
def default_discovery(self):
@ -91,7 +98,8 @@ class _Base(plugin_base.PollsterBase):
from ceilometer.objectstore import rgw_client as c_rgw_client
rgw_client = c_rgw_client.RGWAdminClient(endpoint,
self.access_key,
self.secret)
self.secret,
self.implicit_tenants)
except ImportError:
raise plugin_base.PollsterPermanentError(tenants)

View File

@ -30,11 +30,12 @@ class RGWAdminAPIFailed(Exception):
class RGWAdminClient(object):
Bucket = namedtuple('Bucket', 'name, num_objects, size')
def __init__(self, endpoint, access_key, secret_key):
def __init__(self, endpoint, access_key, secret_key, implicit_tenants):
self.access_key = access_key
self.secret = secret_key
self.endpoint = endpoint
self.hostname = urlparse.urlparse(endpoint).netloc
self.implicit_tenants = implicit_tenants
def _make_request(self, path, req_params):
uri = "{0}/{1}".format(self.endpoint, path)
@ -51,8 +52,12 @@ class RGWAdminClient(object):
return r.json()
def get_bucket(self, tenant_id):
if self.implicit_tenants:
rgw_uid = tenant_id + "$" + tenant_id
else:
rgw_uid = tenant_id
path = "bucket"
req_params = {"uid": tenant_id, "stats": "true"}
req_params = {"uid": rgw_uid, "stats": "true"}
json_data = self._make_request(path, req_params)
stats = {'num_buckets': 0, 'buckets': [], 'size': 0, 'num_objects': 0}
stats['num_buckets'] = len(json_data)
@ -66,8 +71,12 @@ class RGWAdminClient(object):
return stats
def get_usage(self, tenant_id):
if self.implicit_tenants:
rgw_uid = tenant_id + "$" + tenant_id
else:
rgw_uid = tenant_id
path = "usage"
req_params = {"uid": tenant_id}
req_params = {"uid": rgw_uid}
json_data = self._make_request(path, req_params)
usage_data = json_data["summary"]
return sum((it["total"]["ops"] for it in usage_data))

View File

@ -130,6 +130,7 @@ def list_opts():
('publisher', ceilometer.publisher.utils.OPTS),
('publisher_notifier', ceilometer.publisher.messaging.NOTIFIER_OPTS),
('rgw_admin_credentials', ceilometer.objectstore.rgw.CREDENTIAL_OPTS),
('rgw_client', ceilometer.objectstore.rgw.CLIENT_OPTS),
('service_types',
itertools.chain(ceilometer.image.discovery.SERVICE_OPTS,
ceilometer.neutron_client.SERVICE_OPTS,

View File

@ -152,7 +152,7 @@ class TestRGWAdminClient(base.BaseTestCase):
def setUp(self):
super(TestRGWAdminClient, self).setUp()
self.client = rgw_client.RGWAdminClient('http://127.0.0.1:8080/admin',
'abcde', 'secret')
'abcde', 'secret', False)
self.get_resp = mock.MagicMock()
self.get = mock.patch('requests.get',
return_value=self.get_resp).start()
@ -179,6 +179,24 @@ class TestRGWAdminClient(base.BaseTestCase):
expected = {'num_buckets': 2, 'size': 1042, 'num_objects': 1001,
'buckets': bucket_list}
self.assertEqual(expected, actual)
self.assertEqual(1, len(self.get.call_args_list))
self.assertEqual('foo',
self.get.call_args_list[0][1]['params']['uid'])
def test_get_buckets_implicit_tenants(self):
self.get_resp.status_code = 200
self.get_resp.json.return_value = buckets_json
self.client.implicit_tenants = True
actual = self.client.get_bucket('foo')
bucket_list = [rgw_client.RGWAdminClient.Bucket('somefoo', 1000, 1000),
rgw_client.RGWAdminClient.Bucket('somefoo31', 1, 42),
]
expected = {'num_buckets': 2, 'size': 1042, 'num_objects': 1001,
'buckets': bucket_list}
self.assertEqual(expected, actual)
self.assertEqual(1, len(self.get.call_args_list))
self.assertEqual('foo$foo',
self.get.call_args_list[0][1]['params']['uid'])
def test_get_usage(self):
self.get_resp.status_code = 200
@ -186,3 +204,17 @@ class TestRGWAdminClient(base.BaseTestCase):
actual = self.client.get_usage('foo')
expected = 7
self.assertEqual(expected, actual)
self.assertEqual(1, len(self.get.call_args_list))
self.assertEqual('foo',
self.get.call_args_list[0][1]['params']['uid'])
def test_get_usage_implicit_tenants(self):
self.get_resp.status_code = 200
self.get_resp.json.return_value = usage_json
self.client.implicit_tenants = True
actual = self.client.get_usage('foo')
expected = 7
self.assertEqual(expected, actual)
self.assertEqual(1, len(self.get.call_args_list))
self.assertEqual('foo$foo',
self.get.call_args_list[0][1]['params']['uid'])