diff --git a/kingbird/common/config.py b/kingbird/common/config.py index 0394072..f1eab3e 100644 --- a/kingbird/common/config.py +++ b/kingbird/common/config.py @@ -35,6 +35,7 @@ neutron_quotas = [ cfg.IntOpt('quota_subnet', default=10), cfg.IntOpt('quota_port', default=50), cfg.IntOpt('quota_security_group', default=10), + cfg.IntOpt('quota_security_group_rule', default=100), cfg.IntOpt('quota_router', default=10), cfg.IntOpt('quota_floatingip', default=50) ] diff --git a/kingbird/drivers/openstack/neutron_v2.py b/kingbird/drivers/openstack/neutron_v2.py index 4a0a532..d110a11 100644 --- a/kingbird/drivers/openstack/neutron_v2.py +++ b/kingbird/drivers/openstack/neutron_v2.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import defaultdict + from oslo_log import log from kingbird.common import exceptions @@ -23,26 +25,74 @@ API_VERSION = '2.0' class NeutronClient(base.DriverBase): '''Neutron V2 driver.''' - def __init__(self, region, session): + def __init__(self, region, disabled_quotas, session): try: - self.neutron_client = client.Client( + self.neutron = client.Client( API_VERSION, session=session, region_name=region) - self.extension_list = self.neutron_client.list_extensions() + self.extension_list = self.neutron.list_extensions() + self.disabled_quotas = disabled_quotas + self.no_network = True if 'floatingip' in self.disabled_quotas \ + else False + self.is_sec_group_enabled = self.is_extension_supported( + 'security-group') except exceptions.ServiceUnavailable: raise def get_resource_usages(self, project_id): - '''Calcualte resources usage and return the dict''' - return {} + '''Calcualte resources usage and return the dict + + :param: project_id + :return: resource usage dict + ''' + if not self.no_network: + try: + usages = defaultdict(dict) + + opts = {'tenant_id': project_id} + + networks = self.neutron.list_networks(**opts)['networks'] + subnets = self.neutron.list_subnets(**opts)['subnets'] + ports = self.neutron.list_ports(**opts)['ports'] + routers = self.neutron.list_routers(**opts)['routers'] + floatingips = self.neutron.list_floatingips( + **opts)['floatingips'] + + usages['network'] = len(networks) + usages['subnet'] = len(subnets) + usages['port'] = len(ports) + usages['router'] = len(routers) + usages['floatingip'] = len(floatingips) + + if self.is_sec_group_enabled: + security_group_rules = \ + self.neutron.list_security_group_rules( + **opts)['security_group_rules'] + security_groups = self.neutron.list_security_groups( + **opts)['security_groups'] + usages['security_group_rule'] = len(security_group_rules) + usages['security_group'] = len(security_groups) + return usages + + except exceptions.InternalError: + raise def update_quota_limits(self, project_id, new_quota): '''Update the limits''' - pass + try: + if not self.no_network: + return self.neutron.update_quota(project_id, + {"quota": new_quota}) + except exceptions.InternalError: + raise def delete_quota_limits(self, project_id): '''Delete/Reset the limits''' - pass + try: + if not self.no_network: + return self.neutron.delete_quota(project_id) + except exceptions.InternalError: + raise def is_extension_supported(self, extension): for current_extension in self.extension_list['extensions']: diff --git a/kingbird/drivers/openstack/sdk.py b/kingbird/drivers/openstack/sdk.py index ade649c..d0899cd 100644 --- a/kingbird/drivers/openstack/sdk.py +++ b/kingbird/drivers/openstack/sdk.py @@ -47,6 +47,7 @@ class OpenStackDriver(object): else: self.keystone_client = KeystoneClient() OpenStackDriver.os_clients_dict['keystone'] = self.keystone_client + self.disabled_quotas = self._get_disabled_quotas(region_name) if region_name in OpenStackDriver.os_clients_dict: LOG.info(_LI('Using cached OS client objects')) self.nova_client = OpenStackDriver.os_clients_dict[ @@ -59,9 +60,10 @@ class OpenStackDriver(object): # Create new objects and cache them LOG.info(_("Creating fresh OS Clients objects")) self.neutron_client = NeutronClient(region_name, + self.disabled_quotas, self.keystone_client.session) - self.disabled_quotas = self._get_disabled_quotas(region_name) - self.nova_client = NovaClient(region_name, self.disabled_quotas, + self.nova_client = NovaClient(region_name, + self.disabled_quotas, self.keystone_client.session) self.cinder_client = CinderClient(region_name, self.disabled_quotas, @@ -101,9 +103,8 @@ class OpenStackDriver(object): **limits_to_write['nova']) self.cinder_client.update_quota_limits(project_id, **limits_to_write['cinder']) - # TODO(Ashish): Include other clients after nova is fixed - # self.neutron_client.update_quota_limits(project_id, - # **limits_to_write['neutron']) + self.neutron_client.update_quota_limits(project_id, + limits_to_write['neutron']) except (exceptions.ConnectionRefused, exceptions.NotAuthorized, exceptions.TimeOut): # Delete the cached objects for that region @@ -132,22 +133,9 @@ class OpenStackDriver(object): if not self.keystone_client.is_service_enabled('network'): disabled_quotas.extend(consts.NEUTRON_QUOTA_FIELDS) else: - # Remove the nova network quotas disabled_quotas.extend(['floating_ips', 'fixed_ips']) - if self.neutron_client.is_extension_supported('security-group'): - # If Neutron security group is supported, disable Nova quotas - disabled_quotas.extend(['security_groups', - 'security_group_rules']) - else: - # If Nova security group is used, disable Neutron quotas - disabled_quotas.extend(['security_group', - 'security_group_rule']) - try: - if not self.neutron_client.is_extension_supported('quotas'): - disabled_quotas.extend(consts.NEUTRON_QUOTA_FIELDS) - except Exception: - LOG.error("There was an error checking if the Neutron " - "quotas extension is enabled.") + disabled_quotas.extend(['security_groups', + 'security_group_rules']) return disabled_quotas def get_all_regions_for_project(self, project_id): diff --git a/kingbird/tests/unit/drivers/test_neutron_v2.py b/kingbird/tests/unit/drivers/test_neutron_v2.py index 16e18e1..2a58d92 100644 --- a/kingbird/tests/unit/drivers/test_neutron_v2.py +++ b/kingbird/tests/unit/drivers/test_neutron_v2.py @@ -1,3 +1,4 @@ +# Copyright (c) 2016 Ericsson AB # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -18,9 +19,16 @@ from kingbird.tests import utils FAKE_EXTENSIONS = { - 'extensions': ['fake_extension1', - 'fake_extension2'] + 'extensions': [{'name': 'fake_extension1', 'alias': 'fake_alias1'}, + {'name': 'fake_extension2', 'alias': 'fake_alias2'}] } +FAKE_NETWORKS = {'networks': ['net1', 'net2']} +FAKE_SUBNETS = {'subnets': ['subnet1', 'subnet2']} +FAKE_PORTS = {'ports': ['port1', 'port2']} +FAKE_ROUTERS = {'routers': ['router1', 'router2']} +FAKE_FLOATINGIPS = {'floatingips': ['fp1', 'fp2']} +FAKE_SEC_GRP_RULES = {'security_group_rules': ['sec_grp_rul1', 'sec_grp_rul2']} +FAKE_SEC_GRP = {'security_groups': ['sec_grp1', 'sec_grp2']} class TestNeutronClient(base.KingbirdTestCase): @@ -28,28 +36,79 @@ class TestNeutronClient(base.KingbirdTestCase): super(TestNeutronClient, self).setUp() self.ctx = utils.dummy_context() self.session = 'fake_session' + self.project = 'fake_project' + self.disabled_quota = ['floating_ips', 'volumes'] - @mock.patch.object(neutron_v2, 'NeutronClient') + @mock.patch.object(neutron_v2, 'client') def test_init(self, mock_neutron): - mock_neutron().extension_list = FAKE_EXTENSIONS + mock_neutron.Client().list_extensions.return_value = FAKE_EXTENSIONS neutron_client = neutron_v2.NeutronClient('fake_region', + self.disabled_quota, self.session) + self.assertIsNotNone(neutron_client.neutron) self.assertEqual(FAKE_EXTENSIONS, neutron_client.extension_list) - @mock.patch.object(neutron_v2, 'NeutronClient') + @mock.patch.object(neutron_v2, 'client') def test_is_extension_supported(self, mock_neutron): + mock_neutron.Client().list_extensions.return_value = FAKE_EXTENSIONS neutron_client = neutron_v2.NeutronClient('fake_region', + self.disabled_quota, self.session) - mock_neutron().is_extension_supported.return_value = True - extension_enabled = neutron_client.is_extension_supported('quotas') - self.assertEqual(extension_enabled, True) + actual_value = neutron_client.is_extension_supported('fake_alias1') + self.assertEqual(True, actual_value) - def test_get_resource_usages(self): - pass + @mock.patch.object(neutron_v2, 'client') + def test_extension_not_supported(self, mock_neutron): + mock_neutron.Client().list_extensions.return_value = FAKE_EXTENSIONS + neutron_client = neutron_v2.NeutronClient('fake_region', + self.disabled_quota, + self.session) + actual_value = neutron_client.is_extension_supported( + 'not_supported_alias') + self.assertEqual(False, actual_value) - def test_update_quota_limits(self): - pass + @mock.patch.object(neutron_v2, 'client') + def test_get_resource_usages(self, mock_neutron): + mock_neutron.Client().list_networks.return_value = FAKE_NETWORKS + mock_neutron.Client().list_subnets.return_value = FAKE_SUBNETS + mock_neutron.Client().list_ports.return_value = FAKE_PORTS + mock_neutron.Client().list_routers.return_value = FAKE_ROUTERS + mock_neutron.Client().list_floatingips.return_value = FAKE_FLOATINGIPS + mock_neutron.Client().list_security_group_rules.return_value = \ + FAKE_SEC_GRP_RULES + mock_neutron.Client().list_security_groups.return_value = FAKE_SEC_GRP + neutron_client = neutron_v2.NeutronClient('fake_region', + self.disabled_quota, + self.session) + setattr(neutron_client, 'is_sec_group_enabled', True) + total_neutron_usages = neutron_client.get_resource_usages( + self.project) + self.assertEqual(2, total_neutron_usages['port']) + self.assertEqual(2, total_neutron_usages['router']) + self.assertEqual(2, total_neutron_usages['security_group']) + self.assertEqual(2, total_neutron_usages['security_group_rule']) + self.assertEqual(2, total_neutron_usages['floatingip']) + self.assertEqual(2, total_neutron_usages['network']) + self.assertEqual(2, total_neutron_usages['subnet']) - def test_delete_quota_limits(self): - pass + @mock.patch.object(neutron_v2, 'client') + def test_update_quota_limits(self, mock_neutron): + neutron_client = neutron_v2.NeutronClient('fake_region', + self.disabled_quota, + self.session) + new_quota = {'subnets': 4, 'ports': 3} + setattr(neutron_client, 'no_network', False) + neutron_client.update_quota_limits(self.project, new_quota) + mock_neutron.Client().update_quota.assert_called_once_with( + self.project, {"quota": new_quota}) + + @mock.patch.object(neutron_v2, 'client') + def test_delete_quota_limits(self, mock_neutron): + neutron_client = neutron_v2.NeutronClient('fake_region', + self.disabled_quota, + self.session) + setattr(neutron_client, 'no_network', False) + neutron_client.delete_quota_limits(self.project) + mock_neutron.Client().delete_quota.assert_called_once_with( + self.project) diff --git a/kingbird/tests/unit/drivers/test_openstack_driver.py b/kingbird/tests/unit/drivers/test_openstack_driver.py index 91ee610..d1eb4d6 100644 --- a/kingbird/tests/unit/drivers/test_openstack_driver.py +++ b/kingbird/tests/unit/drivers/test_openstack_driver.py @@ -86,12 +86,12 @@ class TestOpenStackDriver(base.KingbirdTestCase): ).update_quota_limits.assert_called_once_with(project_id, instances=7, ram=1222, vcpus=10) - # mock_network_client( - # ).update_quota_limits.assert_called_once_with(project_id, - # write_limits['neutron']) - # mock_cinder_client( - # ).update_quota_limits.assert_called_once_with(project_id, - # write_limits['cinder']) + mock_network_client( + ).update_quota_limits.assert_called_once_with(project_id, + write_limits['neutron']) + mock_cinder_client( + ).update_quota_limits.assert_called_once_with(project_id, + **write_limits['cinder']) @mock.patch.object(sdk, 'KeystoneClient') @mock.patch.object(sdk, 'NovaClient')