From 79c0075b2e3872f9cde8101403b19bd963f7f992 Mon Sep 17 00:00:00 2001 From: spisarski Date: Mon, 7 Aug 2017 14:14:06 -0600 Subject: Added feature to update the quotas on a project/tenant. JIRA: SNAPS-170 Change-Id: Icf494dd2bddc338b8e85259b0400c0950d2332bc Signed-off-by: spisarski --- docs/how-to-use/APITests.rst | 3 ++ docs/how-to-use/UnitTests.rst | 12 +++++ snaps/domain/project.py | 72 ++++++++++++++++++++++++++- snaps/domain/test/project_tests.py | 67 ++++++++++++++++++++++++- snaps/openstack/create_project.py | 35 ++++++++++++- snaps/openstack/tests/create_project_tests.py | 47 ++++++++++++++++- snaps/openstack/utils/keystone_utils.py | 6 ++- snaps/openstack/utils/neutron_utils.py | 34 +++++++++++++ snaps/openstack/utils/nova_utils.py | 35 +++++++++++++ snaps/test_suite_builder.py | 7 ++- 10 files changed, 311 insertions(+), 7 deletions(-) diff --git a/docs/how-to-use/APITests.rst b/docs/how-to-use/APITests.rst index ff0ab45..0d4239f 100644 --- a/docs/how-to-use/APITests.rst +++ b/docs/how-to-use/APITests.rst @@ -105,6 +105,9 @@ create_project_tests.py - CreateProjectSuccessTests | | | OpenStackProject class to ensure that clean will not raise| | | | an exception | +----------------------------------+---------------+-----------------------------------------------------------+ +| test_update_quotas | 2 & 3 | Tests the ability to update quota values | +| | nova & neutron| | ++----------------------------------+---------------+-----------------------------------------------------------+ create_project_tests.py - CreateProjectUserTests ------------------------------------------------ diff --git a/docs/how-to-use/UnitTests.rst b/docs/how-to-use/UnitTests.rst index ac42892..cdf466e 100644 --- a/docs/how-to-use/UnitTests.rst +++ b/docs/how-to-use/UnitTests.rst @@ -126,6 +126,18 @@ DomainDomainObjectTests Ensures that all required members are included when constructing a Domain domain object +ComputeQuotasDomainObjectTests +------------------------------ + +Ensures that all required members are included when constructing a +ComputeQuotas domain object + +NetworkQuotasDomainObjectTests +------------------------------ + +Ensures that all required members are included when constructing a +NetworkQuotas domain object + RoleDomainObjectTests --------------------- diff --git a/snaps/domain/project.py b/snaps/domain/project.py index 54407cf..aa125e3 100644 --- a/snaps/domain/project.py +++ b/snaps/domain/project.py @@ -16,7 +16,7 @@ class Project: """ - SNAPS domain object for Projects. Should contain attributes that + SNAPS domain class for Projects. Should contain attributes that are shared amongst cloud providers """ def __init__(self, name, project_id, domain_id=None): @@ -36,7 +36,7 @@ class Project: class Domain: """ - SNAPS domain object for OpenStack Keystone v3+ domains. + SNAPS domain class for OpenStack Keystone v3+ domains. """ def __init__(self, name, domain_id=None): """ @@ -49,3 +49,71 @@ class Domain: def __eq__(self, other): return self.name == other.name and self.id == other.id + + +class ComputeQuotas: + """ + SNAPS domain class for holding project quotas for compute services + """ + def __init__(self, nova_quotas=None, **kwargs): + """ + Constructor + :param nova_quotas: the OS nova quota object + """ + if nova_quotas: + self.metadata_items = nova_quotas.metadata_items + self.cores = nova_quotas.cores # aka. VCPUs + self.instances = nova_quotas.instances + self.injected_files = nova_quotas.injected_files + self.injected_file_content_bytes = nova_quotas.injected_file_content_bytes + self.ram = nova_quotas.ram + self.fixed_ips = nova_quotas.fixed_ips + self.key_pairs = nova_quotas.key_pairs + else: + self.metadata_items = kwargs.get('metadata_items') + self.cores = kwargs.get('cores') # aka. VCPUs + self.instances = kwargs.get('instances') + self.injected_files = kwargs.get('injected_files') + self.injected_file_content_bytes = kwargs.get( + 'injected_file_content_bytes') + self.ram = kwargs.get('ram') + self.fixed_ips = kwargs.get('fixed_ips') + self.key_pairs = kwargs.get('key_pairs') + + def __eq__(self, other): + return (self.metadata_items == other.metadata_items and + self.cores == other.cores and + self.instances == other.instances and + self.injected_files == other.injected_files and + self.injected_file_content_bytes == other.injected_file_content_bytes and + self.fixed_ips == other.fixed_ips and + self.key_pairs == other.key_pairs) + + +class NetworkQuotas: + """ + SNAPS domain class for holding project quotas for networking services + """ + def __init__(self, **neutron_quotas): + """ + Constructor + :param neutron_quotas: the OS network quota object + """ + + # Networks settings here + self.security_group = neutron_quotas['security_group'] + self.security_group_rule = neutron_quotas['security_group_rule'] + self.floatingip = neutron_quotas['floatingip'] + self.network = neutron_quotas['network'] + self.port = neutron_quotas['port'] + self.router = neutron_quotas['router'] + self.subnet = neutron_quotas['subnet'] + + def __eq__(self, other): + return (self.security_group == other.security_group and + self.security_group_rule == other.security_group_rule and + self.floatingip == other.floatingip and + self.network == other.network and + self.port == other.port and + self.router == other.router and + self.subnet == other.subnet) diff --git a/snaps/domain/test/project_tests.py b/snaps/domain/test/project_tests.py index 3f4fca6..d0aec3a 100644 --- a/snaps/domain/test/project_tests.py +++ b/snaps/domain/test/project_tests.py @@ -14,7 +14,7 @@ # limitations under the License. import unittest -from snaps.domain.project import Project, Domain +from snaps.domain.project import Project, Domain, ComputeQuotas, NetworkQuotas class ProjectDomainObjectTests(unittest.TestCase): @@ -61,3 +61,68 @@ class DomainDomainObjectTests(unittest.TestCase): domain = Domain(domain_id='123-456', name='foo') self.assertEqual('foo', domain.name) self.assertEqual('123-456', domain.id) + + +class ComputeQuotasDomainObjectTests(unittest.TestCase): + """ + Tests the construction of the snaps.domain.project.ComputeQuotas class + """ + + def test_construction_positional(self): + quotas = ComputeQuotas( + metadata_items=64, cores=5, instances= 4, injected_files= 3, + injected_file_content_bytes=5120,ram=25600, fixed_ips=100, + key_pairs=50) + self.assertEqual(64, quotas.metadata_items) + self.assertEqual(5, quotas.cores) + self.assertEqual(4, quotas.instances) + self.assertEqual(3, quotas.injected_files) + self.assertEqual(5120, quotas.injected_file_content_bytes) + self.assertEqual(25600, quotas.ram) + self.assertEqual(100, quotas.fixed_ips) + self.assertEqual(50, quotas.key_pairs) + + def test_construction_named_minimal(self): + quotas = ComputeQuotas( + **{'metadata_items': 64, 'cores': 5, 'instances': 4, + 'injected_files': 3, 'injected_file_content_bytes': 5120, + 'ram': 25600, 'fixed_ips': 100, 'key_pairs': 50}) + self.assertEqual(64, quotas.metadata_items) + self.assertEqual(5, quotas.cores) + self.assertEqual(4, quotas.instances) + self.assertEqual(3, quotas.injected_files) + self.assertEqual(5120, quotas.injected_file_content_bytes) + self.assertEqual(25600, quotas.ram) + self.assertEqual(100, quotas.fixed_ips) + self.assertEqual(50, quotas.key_pairs) + + +class NetworkQuotasDomainObjectTests(unittest.TestCase): + """ + Tests the construction of the snaps.domain.project.NetworkQuotas class + """ + + def test_construction_positional(self): + quotas = NetworkQuotas( + security_group=5, security_group_rule=50, + floatingip=25, network=5, port=25, router=6, subnet=7) + self.assertEqual(5, quotas.security_group) + self.assertEqual(50, quotas.security_group_rule) + self.assertEqual(25, quotas.floatingip) + self.assertEqual(5, quotas.network) + self.assertEqual(25, quotas.port) + self.assertEqual(6, quotas.router) + self.assertEqual(7, quotas.subnet) + + def test_construction_named_minimal(self): + quotas = NetworkQuotas( + **{'security_group': 5, 'security_group_rule': 50, + 'floatingip': 25, 'network': 5, 'port': 25, 'router': 6, + 'subnet': 7}) + self.assertEqual(5, quotas.security_group) + self.assertEqual(50, quotas.security_group_rule) + self.assertEqual(25, quotas.floatingip) + self.assertEqual(5, quotas.network) + self.assertEqual(25, quotas.port) + self.assertEqual(6, quotas.router) + self.assertEqual(7, quotas.subnet) diff --git a/snaps/openstack/create_project.py b/snaps/openstack/create_project.py index 7eebbe0..7bfdad1 100644 --- a/snaps/openstack/create_project.py +++ b/snaps/openstack/create_project.py @@ -15,7 +15,8 @@ import logging from keystoneclient.exceptions import NotFound -from snaps.openstack.utils import keystone_utils, neutron_utils + +from snaps.openstack.utils import keystone_utils, neutron_utils, nova_utils __author__ = 'spisarski' @@ -112,6 +113,38 @@ class OpenStackProject: keystone_utils.grant_user_role_to_project(self.__keystone, self.__role, user, self.__project) + def get_compute_quotas(self): + """ + Returns the compute quotas as an instance of the ComputeQuotas class + :return: + """ + nova = nova_utils.nova_client(self.__os_creds) + return nova_utils.get_compute_quotas(nova, self.__project.id) + + def get_network_quotas(self): + """ + Returns the network quotas as an instance of the NetworkQuotas class + :return: + """ + neutron = neutron_utils.neutron_client(self.__os_creds) + return neutron_utils.get_network_quotas(neutron, self.__project.id) + + def update_compute_quotas(self, compute_quotas): + """ + Updates the compute quotas for this project + :param compute_quotas: a ComputeQuotas object. + """ + nova = nova_utils.nova_client(self.__os_creds) + nova_utils.update_quotas(nova, self.__project.id, compute_quotas) + + def update_network_quotas(self, network_quotas): + """ + Updates the network quotas for this project + :param network_quotas: a NetworkQuotas object. + """ + neutron = neutron_utils.neutron_client(self.__os_creds) + neutron_utils.update_quotas(neutron, self.__project.id, network_quotas) + class ProjectSettings: """ diff --git a/snaps/openstack/tests/create_project_tests.py b/snaps/openstack/tests/create_project_tests.py index b225e3d..dfbb0d6 100644 --- a/snaps/openstack/tests/create_project_tests.py +++ b/snaps/openstack/tests/create_project_tests.py @@ -17,6 +17,7 @@ import uuid from keystoneclient.exceptions import BadRequest +from snaps.domain.project import ComputeQuotas, NetworkQuotas from snaps.openstack.create_project import ( OpenStackProject, ProjectSettings, ProjectSettingsError) from snaps.openstack.create_security_group import OpenStackSecurityGroup @@ -24,7 +25,7 @@ from snaps.openstack.create_security_group import SecurityGroupSettings from snaps.openstack.create_user import OpenStackUser from snaps.openstack.create_user import UserSettings from snaps.openstack.tests.os_source_file_test import OSComponentTestCase -from snaps.openstack.utils import keystone_utils +from snaps.openstack.utils import keystone_utils, nova_utils, neutron_utils __author__ = 'spisarski' @@ -171,6 +172,50 @@ class CreateProjectSuccessTests(OSComponentTestCase): self.assertTrue(validate_project(self.keystone, self.project_settings, created_project)) + def test_update_quotas(self): + """ + Tests the creation of an OpenStack project where the quotas get + updated. + """ + self.project_creator = OpenStackProject(self.os_creds, + self.project_settings) + created_project = self.project_creator.create() + self.assertIsNotNone(created_project) + + retrieved_project = keystone_utils.get_project( + keystone=self.keystone, project_settings=self.project_settings) + self.assertIsNotNone(retrieved_project) + self.assertEqual(created_project, retrieved_project) + self.assertTrue(validate_project(self.keystone, self.project_settings, + created_project)) + + update_compute_quotas = ComputeQuotas( + **{'metadata_items': 64, 'cores': 5, 'instances': 5, + 'injected_files': 3, 'injected_file_content_bytes': 5120, + 'ram': 25600, 'fixed_ips': 100, 'key_pairs': 50}) + self.project_creator.update_compute_quotas(update_compute_quotas) + + update_network_quotas = NetworkQuotas( + **{'security_group': 5, 'security_group_rule': 50, + 'floatingip': 25, 'network': 5, 'port': 25, 'router': 6, + 'subnet': 7}) + self.project_creator.update_network_quotas(update_network_quotas) + + self.assertEqual(update_compute_quotas, + self.project_creator.get_compute_quotas()) + self.assertEqual(update_network_quotas, + self.project_creator.get_network_quotas()) + + nova = nova_utils.nova_client(self.os_creds) + new_compute_quotas = nova_utils.get_compute_quotas( + nova, self.project_creator.get_project().id) + self.assertEqual(update_compute_quotas, new_compute_quotas) + + neutron = neutron_utils.neutron_client(self.os_creds) + new_network_quotas = neutron_utils.get_network_quotas( + neutron, self.project_creator.get_project().id) + self.assertEqual(update_network_quotas, new_network_quotas) + class CreateProjectUserTests(OSComponentTestCase): """ diff --git a/snaps/openstack/utils/keystone_utils.py b/snaps/openstack/utils/keystone_utils.py index 10ad68a..f390c0f 100644 --- a/snaps/openstack/utils/keystone_utils.py +++ b/snaps/openstack/utils/keystone_utils.py @@ -107,7 +107,9 @@ def get_endpoint(os_creds, service_type, interface='public'): def get_project(keystone=None, os_creds=None, project_settings=None, project_name=None): """ - Returns the first project object or None if not found + Returns the first project where the project_settings is used for the query + if not None, else the project_name parameter is used for the query. If both + parameters are None, None is returned :param keystone: the Keystone client :param os_creds: the OpenStack credentials used to obtain the Keystone client if the keystone parameter is None @@ -131,6 +133,8 @@ def get_project(keystone=None, os_creds=None, project_settings=None, proj_filter['description'] = project_settings.description proj_filter['domain_name'] = project_settings.domain_name proj_filter['enabled'] = project_settings.enabled + else: + return None if keystone.version == V2_VERSION_STR: projects = keystone.tenants.list() diff --git a/snaps/openstack/utils/neutron_utils.py b/snaps/openstack/utils/neutron_utils.py index e7b002a..c615bd5 100644 --- a/snaps/openstack/utils/neutron_utils.py +++ b/snaps/openstack/utils/neutron_utils.py @@ -20,6 +20,7 @@ from neutronclient.neutron.client import Client from snaps.domain.network import ( Port, SecurityGroup, SecurityGroupRule, Router, InterfaceRouter, Subnet, Network) +from snaps.domain.project import NetworkQuotas from snaps.domain.vm_inst import FloatingIp from snaps.openstack.utils import keystone_utils @@ -615,6 +616,39 @@ def delete_floating_ip(neutron, floating_ip): return neutron.delete_floatingip(floating_ip.id) +def get_network_quotas(neutron, project_id): + """ + Returns a list of all available keypairs + :param nova: the Nova client + :param project_id: the project's ID of the quotas to lookup + :return: an object of type NetworkQuotas or None if not found + """ + quota = neutron.show_quota(project_id) + if quota: + return NetworkQuotas(**quota['quota']) + + +def update_quotas(neutron, project_id, network_quotas): + """ + Updates the networking quotas for a given project + :param neutron: the Neutron client + :param project_id: the project's ID that requires quota updates + :param network_quotas: an object of type NetworkQuotas containing the + values to update + :return: + """ + update_body = dict() + update_body['security_group'] = network_quotas.security_group + update_body['security_group_rule'] = network_quotas.security_group_rule + update_body['floatingip'] = network_quotas.floatingip + update_body['network'] = network_quotas.network + update_body['port'] = network_quotas.port + update_body['router'] = network_quotas.router + update_body['subnet'] = network_quotas.subnet + + return neutron.update_quota(project_id, {'quota': update_body}) + + class NeutronException(Exception): """ Exception when calls to the Keystone client cannot be served properly diff --git a/snaps/openstack/utils/nova_utils.py b/snaps/openstack/utils/nova_utils.py index 5222712..0a259b0 100644 --- a/snaps/openstack/utils/nova_utils.py +++ b/snaps/openstack/utils/nova_utils.py @@ -24,6 +24,7 @@ from novaclient.exceptions import NotFound from snaps.domain.flavor import Flavor from snaps.domain.keypair import Keypair +from snaps.domain.project import ComputeQuotas from snaps.domain.vm_inst import VmInst from snaps.openstack.utils import keystone_utils, glance_utils, neutron_utils @@ -522,6 +523,40 @@ def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr): vm.add_floating_ip(floating_ip.ip, ip_addr) +def get_compute_quotas(nova, project_id): + """ + Returns a list of all available keypairs + :param nova: the Nova client + :param project_id: the project's ID of the quotas to lookup + :return: an object of type ComputeQuotas or None if not found + """ + quotas = nova.quotas.get(tenant_id=project_id) + if quotas: + return ComputeQuotas(quotas) + + +def update_quotas(nova, project_id, compute_quotas): + """ + Updates the compute quotas for a given project + :param nova: the Nova client + :param project_id: the project's ID that requires quota updates + :param compute_quotas: an object of type ComputeQuotas containing the + values to update + :return: + """ + update_values = dict() + update_values['metadata_items'] = compute_quotas.metadata_items + update_values['cores'] = compute_quotas.cores + update_values['instances'] = compute_quotas.instances + update_values['injected_files'] = compute_quotas.injected_files + update_values['injected_file_content_bytes'] = compute_quotas.injected_file_content_bytes + update_values['ram'] = compute_quotas.ram + update_values['fixed_ips'] = compute_quotas.fixed_ips + update_values['key_pairs'] = compute_quotas.key_pairs + + return nova.quotas.update(project_id, **update_values) + + class NovaException(Exception): """ Exception when calls to the Keystone client cannot be served properly diff --git a/snaps/test_suite_builder.py b/snaps/test_suite_builder.py index b80fcab..f0bae6e 100644 --- a/snaps/test_suite_builder.py +++ b/snaps/test_suite_builder.py @@ -24,7 +24,8 @@ from snaps.domain.test.network_tests import ( PortDomainObjectTests, RouterDomainObjectTests, InterfaceRouterDomainObjectTests, NetworkObjectTests, SubnetObjectTests) from snaps.domain.test.project_tests import ( - ProjectDomainObjectTests, DomainDomainObjectTests) + ProjectDomainObjectTests, DomainDomainObjectTests, + ComputeQuotasDomainObjectTests, NetworkQuotasDomainObjectTests) from snaps.domain.test.role_tests import RoleDomainObjectTests from snaps.domain.test.stack_tests import StackDomainObjectTests from snaps.domain.test.user_tests import UserDomainObjectTests @@ -125,6 +126,10 @@ def add_unit_tests(suite): ProjectDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( DomainDomainObjectTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + ComputeQuotasDomainObjectTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + NetworkQuotasDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( RoleDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( -- cgit 1.2.3-korg