From afdf4d7d46c0ce4e85da12140f9e140d5f4fe490 Mon Sep 17 00:00:00 2001 From: Alain-Christian Courtines <110478358+my-code-AL@users.noreply.github.com> Date: Sun, 14 Jul 2024 23:47:14 -0700 Subject: [PATCH] Add limit configuration for number of projects (#9172) --- .../com/cloud/user/ResourceLimitService.java | 4 + .../user/resource/UpdateResourceLimitCmd.java | 1 + .../java/com/cloud/configuration/Config.java | 9 ++ .../ResourceLimitManagerImpl.java | 6 +- .../ResourceLimitManagerImplTest.java | 98 +++++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/cloud/user/ResourceLimitService.java b/api/src/main/java/com/cloud/user/ResourceLimitService.java index 3b30b8fc4a5..2f4ad1347be 100644 --- a/api/src/main/java/com/cloud/user/ResourceLimitService.java +++ b/api/src/main/java/com/cloud/user/ResourceLimitService.java @@ -46,6 +46,10 @@ public interface ResourceLimitService { "A comma-separated list of tags for host resource limits", true); static final ConfigKey ResourceLimitStorageTags = new ConfigKey<>("Advanced", String.class, "resource.limit.storage.tags", "", "A comma-separated list of tags for storage resource limits", true); + static final ConfigKey DefaultMaxAccountProjects = new ConfigKey<>("Account Defaults",Long.class,"max.account.projects","10", + "The default maximum number of projects that can be created for an account",false); + static final ConfigKey DefaultMaxDomainProjects = new ConfigKey<>("Domain Defaults",Long.class,"max.domain.projects","50", + "The default maximum number of projects that can be created for a domain",false); static final List HostTagsSupportingTypes = List.of(ResourceType.user_vm, ResourceType.cpu, ResourceType.memory); static final List StorageTagsSupportingTypes = List.of(ResourceType.volume, ResourceType.primary_storage); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java index 52afd2b1760..3538a389a6e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java @@ -62,6 +62,7 @@ public class UpdateResourceLimitCmd extends BaseCmd { + "2 - Volume. Number of disk volumes a user can create. " + "3 - Snapshot. Number of snapshots a user can create. " + "4 - Template. Number of templates that a user can register/create. " + + "5 - Project. Number of projects a user can create. " + "6 - Network. Number of guest network a user can create. " + "7 - VPC. Number of VPC a user can create. " + "8 - CPU. Total number of CPU cores a user can use. " diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java index 675e0ee5644..6d9e91dd1ef 100644 --- a/server/src/main/java/com/cloud/configuration/Config.java +++ b/server/src/main/java/com/cloud/configuration/Config.java @@ -1365,6 +1365,14 @@ public enum Config { "200", "The default maximum primary storage space (in GiB) that can be used for an account", null), +DefaultMaxAccountProjects( + "Account Defaults", + ManagementServer.class, + Long.class, + "max.account.projects", + "10", + "The default maximum number of projects that can be created for an account", + null), //disabling lb as cluster sync does not work with distributed cluster SubDomainNetworkAccess( @@ -1414,6 +1422,7 @@ public enum Config { DefaultMaxDomainMemory("Domain Defaults", ManagementServer.class, Long.class, "max.domain.memory", "81920", "The default maximum memory (in MB) that can be used for a domain", null), DefaultMaxDomainPrimaryStorage("Domain Defaults", ManagementServer.class, Long.class, "max.domain.primary.storage", "400", "The default maximum primary storage space (in GiB) that can be used for a domain", null), DefaultMaxDomainSecondaryStorage("Domain Defaults", ManagementServer.class, Long.class, "max.domain.secondary.storage", "800", "The default maximum secondary storage space (in GiB) that can be used for a domain", null), + DefaultMaxDomainProjects("Domain Defaults",ManagementServer.class,Long.class,"max.domain.projects","50","The default maximum number of projects that can be created for a domain",null), DefaultMaxProjectUserVms( "Project Defaults", diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 435604d83ba..b59ddc029ee 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -301,6 +301,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim accountResourceLimitMap.put(Resource.ResourceType.memory.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountMemory.key()))); accountResourceLimitMap.put(Resource.ResourceType.primary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountPrimaryStorage.key()))); accountResourceLimitMap.put(Resource.ResourceType.secondary_storage.name(), MaxAccountSecondaryStorage.value()); + accountResourceLimitMap.put(Resource.ResourceType.project.name(), DefaultMaxAccountProjects.value()); domainResourceLimitMap.put(Resource.ResourceType.public_ip.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainPublicIPs.key()))); domainResourceLimitMap.put(Resource.ResourceType.snapshot.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainSnapshots.key()))); @@ -313,6 +314,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim domainResourceLimitMap.put(Resource.ResourceType.memory.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainMemory.key()))); domainResourceLimitMap.put(Resource.ResourceType.primary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainPrimaryStorage.key()))); domainResourceLimitMap.put(Resource.ResourceType.secondary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainSecondaryStorage.key()))); + domainResourceLimitMap.put(Resource.ResourceType.project.name(), DefaultMaxDomainProjects.value()); } catch (NumberFormatException e) { logger.error("NumberFormatException during configuration", e); throw new ConfigurationException("Configuration failed due to NumberFormatException, see log for the stacktrace"); @@ -2137,7 +2139,9 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim MaxAccountSecondaryStorage, MaxProjectSecondaryStorage, ResourceLimitHostTags, - ResourceLimitStorageTags + ResourceLimitStorageTags, + DefaultMaxAccountProjects, + DefaultMaxDomainProjects }; } diff --git a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java index 3d31561f268..defcd09b174 100644 --- a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java +++ b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java @@ -190,6 +190,12 @@ public class ResourceLimitManagerImplTest extends TestCase { // update resource Limit for a domain for resource_type = 11 (Secondary storage (in GiB)) resourceLimitServiceCall(null, (long)1, 10, (long)400); + + // update resource Limit for an account for resource_type = 5 (Project) + resourceLimitServiceCall((long) 1, (long) 1, 5, (long) 50); + + // update resource Limit for a domain for resource_type = 5 (Project) + resourceLimitServiceCall(null, (long) 1, 5, (long) 100); } private void resourceLimitServiceCall(Long accountId, Long domainId, Integer resourceType, Long max) { @@ -413,6 +419,36 @@ public class ResourceLimitManagerImplTest extends TestCase { Assert.assertEquals(defaultAccountCpuMax, result); } + @Test + public void testFindCorrectResourceLimitForAccountProjects() { + AccountVO account = Mockito.mock(AccountVO.class); + Mockito.when(account.getId()).thenReturn(1L); + Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(true); + + long result = resourceLimitManager.findCorrectResourceLimitForAccount(account, + Resource.ResourceType.project, hostTags.get(0)); + Assert.assertEquals(Resource.RESOURCE_UNLIMITED, result); + + Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(false); + ResourceLimitVO limit = new ResourceLimitVO(); + limit.setMax(10L); + Mockito.when(resourceLimitDao.findByOwnerIdAndTypeAndTag(1L, Resource.ResourceOwnerType.Account, + Resource.ResourceType.project, hostTags.get(0))).thenReturn(limit); + result = resourceLimitManager.findCorrectResourceLimitForAccount(account, Resource.ResourceType.project, + hostTags.get(0)); + Assert.assertEquals(10L, result); + + long defaultAccountProjectsMax = 15L; + Map accountResourceLimitMap = new HashMap<>(); + accountResourceLimitMap.put(Resource.ResourceType.project.name(), defaultAccountProjectsMax); + resourceLimitManager.accountResourceLimitMap = accountResourceLimitMap; + Mockito.when(resourceLimitDao.findByOwnerIdAndTypeAndTag(1L, Resource.ResourceOwnerType.Account, + Resource.ResourceType.project, hostTags.get(0))).thenReturn(null); + result = resourceLimitManager.findCorrectResourceLimitForAccount(account, Resource.ResourceType.project, + hostTags.get(0)); + Assert.assertEquals(defaultAccountProjectsMax, result); + } + @Test public void testFindCorrectResourceLimitForAccountId1() { // long accountId = 1L; @@ -472,6 +508,68 @@ public class ResourceLimitManagerImplTest extends TestCase { Assert.assertEquals(defaultDomainCpuMax, result); } + @Test + public void testResourceUnlimitedForDomainProjects() { + DomainVO domain = Mockito.mock(DomainVO.class); + Mockito.when(domain.getId()).thenReturn(1L); + + long result = resourceLimitManager.findCorrectResourceLimitForDomain(domain, Resource.ResourceType.project, + hostTags.get(0)); + Assert.assertEquals(Resource.RESOURCE_UNLIMITED, result); + } + @Test + public void testSpecificLimitForDomainProjects() { + DomainVO domain = Mockito.mock(DomainVO.class); + Mockito.when(domain.getId()).thenReturn(2L); + + ResourceLimitVO limit = new ResourceLimitVO(); + limit.setMax(100L); + Mockito.when(resourceLimitDao.findByOwnerIdAndTypeAndTag(2L, Resource.ResourceOwnerType.Domain, Resource.ResourceType.project, hostTags.get(0))).thenReturn(limit); + + long result = resourceLimitManager.findCorrectResourceLimitForDomain(domain, Resource.ResourceType.project, hostTags.get(0)); + Assert.assertEquals(100L, result); + } + + @Test + public void testParentDomainLimitForDomainProjects() { + DomainVO domain = Mockito.mock(DomainVO.class); + Mockito.when(domain.getId()).thenReturn(3L); + + DomainVO parentDomain = Mockito.mock(DomainVO.class); + Mockito.when(domain.getParent()).thenReturn(5L); + Mockito.when(domainDao.findById(5L)).thenReturn(parentDomain); + + ResourceLimitVO limit = new ResourceLimitVO(); + limit.setMax(200L); + Mockito.when(resourceLimitDao.findByOwnerIdAndTypeAndTag(3L, Resource.ResourceOwnerType.Domain, + Resource.ResourceType.project, hostTags.get(0))).thenReturn(null); + Mockito.when(resourceLimitDao.findByOwnerIdAndTypeAndTag(5L, Resource.ResourceOwnerType.Domain, + Resource.ResourceType.project, hostTags.get(0))).thenReturn(limit); + + long result = resourceLimitManager.findCorrectResourceLimitForDomain(domain, Resource.ResourceType.project, + hostTags.get(0)); + Assert.assertEquals(200L, result); + } + + @Test + public void testDefaultDomainProjectLimit() { + DomainVO domain = Mockito.mock(DomainVO.class); + Mockito.when(domain.getId()).thenReturn(4L); + Mockito.when(domain.getParent()).thenReturn(null); + + long defaultDomainProjectsMax = 250L; + Map domainResourceLimitMap = new HashMap<>(); + domainResourceLimitMap.put(Resource.ResourceType.project.name(), defaultDomainProjectsMax); + resourceLimitManager.domainResourceLimitMap = domainResourceLimitMap; + + Mockito.when(resourceLimitDao.findByOwnerIdAndTypeAndTag(4L, Resource.ResourceOwnerType.Domain, + Resource.ResourceType.project, hostTags.get(0))).thenReturn(null); + + long result = resourceLimitManager.findCorrectResourceLimitForDomain(domain, Resource.ResourceType.project, + hostTags.get(0)); + Assert.assertEquals(defaultDomainProjectsMax, result); + } + @Test public void testCheckResourceLimitWithTag() { AccountVO account = Mockito.mock(AccountVO.class);