From 246d01beb598f45ae5ac7f8bc6f40249d9253fe5 Mon Sep 17 00:00:00 2001 From: Felix Walter Date: Mon, 11 Jun 2018 15:12:42 +0200 Subject: [PATCH] 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 --- ceilometer/objectstore/rgw.py | 10 +++++- ceilometer/objectstore/rgw_client.py | 15 ++++++-- ceilometer/opts.py | 1 + .../tests/unit/objectstore/test_rgw_client.py | 34 ++++++++++++++++++- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/ceilometer/objectstore/rgw.py b/ceilometer/objectstore/rgw.py index 970aae31bb..3553ab0ac6 100644 --- a/ceilometer/objectstore/rgw.py +++ b/ceilometer/objectstore/rgw.py @@ -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) diff --git a/ceilometer/objectstore/rgw_client.py b/ceilometer/objectstore/rgw_client.py index 6ce9620e0e..daaf1ada93 100644 --- a/ceilometer/objectstore/rgw_client.py +++ b/ceilometer/objectstore/rgw_client.py @@ -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)) diff --git a/ceilometer/opts.py b/ceilometer/opts.py index 837fbd7f6f..d3910138c8 100644 --- a/ceilometer/opts.py +++ b/ceilometer/opts.py @@ -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, diff --git a/ceilometer/tests/unit/objectstore/test_rgw_client.py b/ceilometer/tests/unit/objectstore/test_rgw_client.py index f312068627..cc5958bd3c 100644 --- a/ceilometer/tests/unit/objectstore/test_rgw_client.py +++ b/ceilometer/tests/unit/objectstore/test_rgw_client.py @@ -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'])