diff --git a/etc/moniker/policy.json b/etc/moniker/policy.json index b09b6bd82..54fb59ade 100644 --- a/etc/moniker/policy.json +++ b/etc/moniker/policy.json @@ -17,6 +17,8 @@ "update_tsigkey": "rule:admin", "delete_tsigkey": "rule:admin", + "get_tenants": "rule:admin", + "get_tenant": "rule:admin", "count_tenants": "rule:admin", "create_domain": "rule:admin_or_owner", diff --git a/moniker/api/v1/extensions/reports.py b/moniker/api/v1/extensions/reports.py index 3e9abd7ff..728a4eaf9 100644 --- a/moniker/api/v1/extensions/reports.py +++ b/moniker/api/v1/extensions/reports.py @@ -22,6 +22,24 @@ central_api = central_rpcapi.CentralAPI() blueprint = flask.Blueprint('reports', __name__) +@blueprint.route('/reports/tenants', methods=['GET']) +def reports_tenants(): + context = flask.request.environ.get('context') + + tenants = central_api.get_tenants(context) + + return flask.jsonify(tenants=tenants) + + +@blueprint.route('/reports/tenants/', methods=['GET']) +def reports_tenant(tenant_id): + context = flask.request.environ.get('context') + + tenant = central_api.get_tenant(context, tenant_id) + + return flask.jsonify(tenant) + + @blueprint.route('/reports/counts', methods=['GET']) def reports_counts(): context = flask.request.environ.get('context') diff --git a/moniker/central/rpcapi.py b/moniker/central/rpcapi.py index 4d3158863..caabccb17 100644 --- a/moniker/central/rpcapi.py +++ b/moniker/central/rpcapi.py @@ -28,6 +28,7 @@ class CentralAPI(rpc_proxy.RpcProxy): 1.0 - Initial version 1.1 - Add new finder methods + 1.2 - Add get_tenant and get_tenants """ def __init__(self, topic=None): topic = topic if topic else cfg.CONF.central_topic @@ -88,6 +89,16 @@ class CentralAPI(rpc_proxy.RpcProxy): return self.call(context, msg) # Tenant Methods + def get_tenants(self, context): + msg = self.make_msg('get_tenants') + + return self.call(context, msg, version='1.2') + + def get_tenant(self, context, tenant_id): + msg = self.make_msg('get_tenant', tenant_id=tenant_id) + + return self.call(context, msg, version='1.2') + def count_tenants(self, context): msg = self.make_msg('count_tenants') diff --git a/moniker/central/service.py b/moniker/central/service.py index cb487ea12..063b5090b 100644 --- a/moniker/central/service.py +++ b/moniker/central/service.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) class Service(rpc_service.Service): - RPC_API_VERSION = '1.1' + RPC_API_VERSION = '1.2' def __init__(self, *args, **kwargs): backend_driver = cfg.CONF['service:central'].backend_driver @@ -323,6 +323,19 @@ class Service(rpc_service.Service): return self.storage.delete_tsigkey(context, tsigkey_id) # Tenant Methods + def get_tenants(self, context): + policy.check('get_tenants', context) + return self.storage.get_tenants(context) + + def get_tenant(self, context, tenant_id): + target = { + 'tenant_id': tenant_id + } + + policy.check('get_tenant', context, target) + + return self.storage.get_tenant(context, tenant_id) + def count_tenants(self, context): policy.check('count_tenants', context) return self.storage.count_tenants(context) diff --git a/moniker/storage/base.py b/moniker/storage/base.py index ab896c2e5..ff323954e 100644 --- a/moniker/storage/base.py +++ b/moniker/storage/base.py @@ -114,6 +114,23 @@ class Storage(Plugin): :param tsigkey_id: Delete a TSIG Key via ID """ + @abc.abstractmethod + def get_tenants(self, context): + """ + Get all Tenants. + + :param context: RPC Context. + """ + + @abc.abstractmethod + def get_tenant(self, context, tenant_id): + """ + Get all Tenants. + + :param context: RPC Context. + :param tenant_id: ID of the Tenant. + """ + @abc.abstractmethod def count_tenants(self, context, values): """ diff --git a/moniker/storage/impl_sqlalchemy/__init__.py b/moniker/storage/impl_sqlalchemy/__init__.py index a56e50178..a8a2d09aa 100644 --- a/moniker/storage/impl_sqlalchemy/__init__.py +++ b/moniker/storage/impl_sqlalchemy/__init__.py @@ -15,7 +15,7 @@ # under the License. import time from sqlalchemy.orm import exc -from sqlalchemy import distinct +from sqlalchemy import distinct, func from moniker.openstack.common import cfg from moniker.openstack.common import log as logging from moniker import exceptions @@ -177,6 +177,25 @@ class SQLAlchemyStorage(base.Storage): tsigkey.delete(self.session) # Tenant Methods + def get_tenants(self, context): + query = self.session.query(models.Domain.tenant_id, + func.count(models.Domain.id)) + query = query.group_by(models.Domain.tenant_id) + + return [{'id': t[0], 'domain_count': t[1]} for t in query.all()] + + def get_tenant(self, context, tenant_id): + query = self.session.query(models.Domain.name) + query = query.filter(models.Domain.tenant_id == tenant_id) + + result = query.all() + + return { + 'id': tenant_id, + 'domain_count': len(result), + 'domains': [r[0] for r in result] + } + def count_tenants(self, context): # tenants are the owner of domains, count the number of unique tenants # select count(distinct tenant_id) from domains diff --git a/moniker/tests/__init__.py b/moniker/tests/__init__.py index 82891d51e..0e953d3d8 100644 --- a/moniker/tests/__init__.py +++ b/moniker/tests/__init__.py @@ -65,6 +65,9 @@ class TestCase(unittest2.TestCase): }, { 'name': 'example.net.', 'email': 'example@example.net', + }, { + 'name': 'example.org.', + 'email': 'example@example.org', }] record_fixtures = [ diff --git a/moniker/tests/test_storage/__init__.py b/moniker/tests/test_storage/__init__.py index 4df47e53d..2938e419b 100644 --- a/moniker/tests/test_storage/__init__.py +++ b/moniker/tests/test_storage/__init__.py @@ -291,6 +291,49 @@ class StorageTestCase(TestCase): uuid = 'caf771fc-6b05-4891-bee1-c2a48621f57b' self.storage.delete_tsigkey(self.admin_context, uuid) + # Tenant Tests + def test_get_tenants(self): + # create 3 domains in 2 tenants + self.create_domain(fixture=0, values={'tenant_id': 'One'}) + self.create_domain(fixture=1, values={'tenant_id': 'One'}) + self.create_domain(fixture=2, values={'tenant_id': 'Two'}) + + result = self.storage.get_tenants(self.admin_context) + + expected = [{ + 'id': 'One', + 'domain_count': 2 + }, { + 'id': 'Two', + 'domain_count': 1 + }] + + self.assertEquals(result, expected) + + def test_get_tenant(self): + # create 2 domains in a tenant + _, domain_1 = self.create_domain(fixture=0, values={'tenant_id': 1}) + _, domain_2 = self.create_domain(fixture=1, values={'tenant_id': 1}) + + result = self.storage.get_tenant(self.admin_context, 1) + + self.assertEquals(result['id'], 1) + self.assertEquals(result['domain_count'], 2) + self.assertItemsEqual(result['domains'], + [domain_1['name'], domain_2['name']]) + + def test_count_tenants(self): + # in the beginning, there should be nothing + tenants = self.storage.count_tenants(self.admin_context) + self.assertEqual(tenants, 0) + + # create 2 domains with 2 tenants + self.create_domain(fixture=0, values={'tenant_id': 1}) + self.create_domain(fixture=1, values={'tenant_id': 2}) + + tenants = self.storage.count_tenants(self.admin_context) + self.assertEqual(tenants, 2) + # Domain Tests def test_create_domain(self): values = { @@ -441,6 +484,20 @@ class StorageTestCase(TestCase): uuid = 'caf771fc-6b05-4891-bee1-c2a48621f57b' self.storage.delete_domain(self.admin_context, uuid) + def test_count_domains(self): + # in the beginning, there should be nothing + domains = self.storage.count_domains(self.admin_context) + self.assertEqual(domains, 0) + + # Create a single domain + self.create_domain() + + # count 'em up + domains = self.storage.count_domains(self.admin_context) + + # well, did we get 1? + self.assertEqual(domains, 1) + def test_create_record(self): domain_fixture, domain = self.create_domain() @@ -606,20 +663,6 @@ class StorageTestCase(TestCase): self.assertEqual(pong['status'], True) self.assertIsNotNone(pong['rtt']) - def test_count_domains(self): - # in the beginning, there should be nothing - domains = self.storage.count_domains(self.admin_context) - self.assertEqual(domains, 0) - - # Create a single domain - self.create_domain() - - # count 'em up - domains = self.storage.count_domains(self.admin_context) - - # well, did we get 1? - self.assertEqual(domains, 1) - def test_count_records(self): # in the beginning, there should be nothing records = self.storage.count_records(self.admin_context) @@ -632,15 +675,3 @@ class StorageTestCase(TestCase): # we should have 1 record now records = self.storage.count_domains(self.admin_context) self.assertEqual(records, 1) - - def test_count_tenants(self): - # in the beginning, there should be nothing - tenants = self.storage.count_tenants(self.admin_context) - self.assertEqual(tenants, 0) - - # create 2 domains with 2 tenants - self.create_domain(fixture=0, values={'tenant_id': 1}) - self.create_domain(fixture=1, values={'tenant_id': 2}) - - tenants = self.storage.count_tenants(self.admin_context) - self.assertEqual(tenants, 2)