From 5828e526b3189f53cc13d56b203fcbc822113cb2 Mon Sep 17 00:00:00 2001 From: Sanjay Tripathi Date: Fri, 18 Jan 2013 16:04:13 +0530 Subject: [PATCH] CLOUDSTACK-713: Limit Resources(CPU and Memory) to domain/accounts Addition of two new resource types i.e. CPU and Memory in the existing pool of resource types. Added some methods to set the limits on these resources using updateResourceLimit API command and to get a count using updateResourceCount. Also added calls in the Virtual machine life cycle to check these limits and to increment/decrement the new resource types Resource Name :: Resource type number CPU 8 Memory 9 Also added Unit Tests for the same. --- api/src/com/cloud/configuration/Resource.java | 4 +- .../user/resource/UpdateResourceCountCmd.java | 9 +- .../user/resource/UpdateResourceLimitCmd.java | 8 +- .../api/response/AccountResponse.java | 42 ++++++++++ .../api/response/ResourceCountResponse.java | 2 +- .../api/response/ResourceLimitResponse.java | 2 +- .../api/query/dao/AccountJoinDaoImpl.java | 18 ++++ .../com/cloud/api/query/vo/AccountJoinVO.java | 54 +++++++++++- .../baremetal/BareMetalVmManagerImpl.java | 10 +-- .../src/com/cloud/configuration/Config.java | 4 + .../ResourceLimitManagerImpl.java | 69 +++++++++++++++- .../src/com/cloud/vm/UserVmManagerImpl.java | 82 +++++++++++++------ .../ResourceLimitManagerImplTest.java | 76 +++++++++++++++++ .../vpc/MockResourceLimitManagerImpl.java | 17 +++- setup/db/create-schema-view.sql | 16 ++++ setup/db/db/schema-40to410.sql | 24 ++++++ 16 files changed, 395 insertions(+), 42 deletions(-) create mode 100644 server/test/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java diff --git a/api/src/com/cloud/configuration/Resource.java b/api/src/com/cloud/configuration/Resource.java index 7f551d6b52c..7614c8a4b43 100644 --- a/api/src/com/cloud/configuration/Resource.java +++ b/api/src/com/cloud/configuration/Resource.java @@ -28,7 +28,9 @@ public interface Resource { template("template", 4, ResourceOwnerType.Account, ResourceOwnerType.Domain), project("project", 5, ResourceOwnerType.Account, ResourceOwnerType.Domain), network("network", 6, ResourceOwnerType.Account, ResourceOwnerType.Domain), - vpc("vpc", 7, ResourceOwnerType.Account, ResourceOwnerType.Domain); + vpc("vpc", 7, ResourceOwnerType.Account, ResourceOwnerType.Domain), + cpu("cpu", 8, ResourceOwnerType.Account, ResourceOwnerType.Domain), + memory("memory", 9, ResourceOwnerType.Account, ResourceOwnerType.Domain); private String name; private ResourceOwnerType[] supportedOwners; diff --git a/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java b/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java index 91728ee9cf6..f6d3a98a05d 100644 --- a/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java @@ -53,12 +53,17 @@ public class UpdateResourceCountCmd extends BaseCmd { required=true, description="If account parameter specified then updates resource counts for a specified account in this domain else update resource counts for all accounts & child domains in specified domain.") private Long domainId; - @Parameter(name=ApiConstants.RESOURCE_TYPE, type=CommandType.INTEGER, description= "Type of resource to update. If specifies valid values are 0, 1, 2, 3, and 4. If not specified will update all resource counts" + + @Parameter(name=ApiConstants.RESOURCE_TYPE, type=CommandType.INTEGER, description= "Type of resource to update. If specifies valid values are 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9. If not specified will update all resource counts" + "0 - Instance. Number of instances a user can create. " + "1 - IP. Number of public IP addresses a user can own. " + "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.") + "4 - Template. Number of templates that a user can register/create." + + "5 - Project. Number of projects that 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." + + "9 - Memory. Total Memory (in MB) a user can use." ) private Integer resourceType; @Parameter(name=ApiConstants.PROJECT_ID, type=CommandType.UUID, entityType = ProjectResponse.class, diff --git a/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java b/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java index 33f2574d3e1..0039f6293f7 100644 --- a/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java @@ -54,11 +54,15 @@ public class UpdateResourceLimitCmd extends BaseCmd { @Parameter(name=ApiConstants.MAX, type=CommandType.LONG, description=" Maximum resource limit.") private Long max; - @Parameter(name=ApiConstants.RESOURCE_TYPE, type=CommandType.INTEGER, required=true, description="Type of resource to update. Values are 0, 1, 2, 3, and 4. 0 - Instance. Number of instances a user can create. " + + @Parameter(name=ApiConstants.RESOURCE_TYPE, type=CommandType.INTEGER, required=true, description="Type of resource to update. Values are 0, 1, 2, 3, 4, 6, 7, 8 and 9. 0 - Instance. Number of instances a user can create. " + "1 - IP. Number of public IP addresses a user can own. " + "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.") + "4 - Template. Number of templates that a user can register/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." + + "9 - Memory. Total Memory (in MB) a user can use." ) private Integer resourceType; ///////////////////////////////////////////////////// diff --git a/api/src/org/apache/cloudstack/api/response/AccountResponse.java b/api/src/org/apache/cloudstack/api/response/AccountResponse.java index 0277d5b6fe9..9a98a356492 100644 --- a/api/src/org/apache/cloudstack/api/response/AccountResponse.java +++ b/api/src/org/apache/cloudstack/api/response/AccountResponse.java @@ -132,6 +132,24 @@ public class AccountResponse extends BaseResponse { @SerializedName("vpcavailable") @Param(description="the total number of vpcs available to be created for this account", since="4.0.0") private String vpcAvailable; + @SerializedName("cpulimit") @Param(description="the total number of cpu cores the account can own", since="4.1.0") + private String cpuLimit; + + @SerializedName("cputotal") @Param(description="the total number of cpu cores owned by account", since="4.1.0") + private Long cpuTotal; + + @SerializedName("cpuavailable") @Param(description="the total number of cpu cores available to be created for this account", since="4.1.0") + private String cpuAvailable; + + @SerializedName("memorylimit") @Param(description="the total memory (in MB) the account can own", since="4.1.0") + private String memoryLimit; + + @SerializedName("memorytotal") @Param(description="the total memory (in MB) owned by account", since="4.1.0") + private Long memoryTotal; + + @SerializedName("memoryavailable") @Param(description="the total memory (in MB) available to be created for this account", since="4.1.0") + private String memoryAvailable; + @SerializedName(ApiConstants.STATE) @Param(description="the state of the account") private String state; @@ -294,6 +312,30 @@ public class AccountResponse extends BaseResponse { this.networkAvailable = networkAvailable; } + public void setCpuLimit(String cpuLimit) { + this.cpuLimit = cpuLimit; + } + + public void setCpuTotal(Long cpuTotal) { + this.cpuTotal = cpuTotal; + } + + public void setCpuAvailable(String cpuAvailable) { + this.cpuAvailable = cpuAvailable; + } + + public void setMemoryLimit(String memoryLimit) { + this.memoryLimit = memoryLimit; + } + + public void setMemoryTotal(Long memoryTotal) { + this.memoryTotal = memoryTotal; + } + + public void setMemoryAvailable(String memoryAvailable) { + this.memoryAvailable = memoryAvailable; + } + public void setDefaultZone(String defaultZoneId) { this.defaultZoneId = defaultZoneId; } diff --git a/api/src/org/apache/cloudstack/api/response/ResourceCountResponse.java b/api/src/org/apache/cloudstack/api/response/ResourceCountResponse.java index 9d4f6c5aad2..a7fbbf2630b 100644 --- a/api/src/org/apache/cloudstack/api/response/ResourceCountResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ResourceCountResponse.java @@ -40,7 +40,7 @@ public class ResourceCountResponse extends BaseResponse implements ControlledEnt @SerializedName(ApiConstants.DOMAIN) @Param(description="the domain name for which resource count's are updated") private String domainName; - @SerializedName(ApiConstants.RESOURCE_TYPE) @Param(description="resource type. Values include 0, 1, 2, 3, 4. See the resourceType parameter for more information on these values.") + @SerializedName(ApiConstants.RESOURCE_TYPE) @Param(description="resource type. Values include 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. See the resourceType parameter for more information on these values.") private String resourceType; @SerializedName("resourcecount") @Param(description="resource count") diff --git a/api/src/org/apache/cloudstack/api/response/ResourceLimitResponse.java b/api/src/org/apache/cloudstack/api/response/ResourceLimitResponse.java index beead247b23..b444e7a68a7 100644 --- a/api/src/org/apache/cloudstack/api/response/ResourceLimitResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ResourceLimitResponse.java @@ -36,7 +36,7 @@ public class ResourceLimitResponse extends BaseResponse implements ControlledEnt @SerializedName(ApiConstants.DOMAIN) @Param(description="the domain name of the resource limit") private String domainName; - @SerializedName(ApiConstants.RESOURCE_TYPE) @Param(description="resource type. Values include 0, 1, 2, 3, 4. See the resourceType parameter for more information on these values.") + @SerializedName(ApiConstants.RESOURCE_TYPE) @Param(description="resource type. Values include 0, 1, 2, 3, 4, 6, 7, 8, 9. See the resourceType parameter for more information on these values.") private String resourceType; @SerializedName("max") @Param(description="the maximum number of the resource. A -1 means the resource currently has no limit.") diff --git a/server/src/com/cloud/api/query/dao/AccountJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/AccountJoinDaoImpl.java index 22b807c9d40..898bafc47cd 100644 --- a/server/src/com/cloud/api/query/dao/AccountJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/AccountJoinDaoImpl.java @@ -157,6 +157,24 @@ public class AccountJoinDaoImpl extends GenericDaoBase impl accountResponse.setNetworkTotal(vpcTotal); accountResponse.setNetworkAvailable(vpcAvail); + //get resource limits for cpu cores + long cpuLimit = ApiDBUtils.findCorrectResourceLimit(account.getCpuLimit(), account.getType(), ResourceType.cpu); + String cpuLimitDisplay = (accountIsAdmin || cpuLimit == -1) ? "Unlimited" : String.valueOf(cpuLimit); + long cpuTotal = (account.getCpuTotal() == null) ? 0 : account.getCpuTotal(); + String cpuAvail = (accountIsAdmin || cpuLimit == -1) ? "Unlimited" : String.valueOf(cpuLimit - cpuTotal); + accountResponse.setCpuLimit(cpuLimitDisplay); + accountResponse.setCpuTotal(cpuTotal); + accountResponse.setCpuAvailable(cpuAvail); + + //get resource limits for memory + long memoryLimit = ApiDBUtils.findCorrectResourceLimit(account.getMemoryLimit(), account.getType(), ResourceType.memory); + String memoryLimitDisplay = (accountIsAdmin || memoryLimit == -1) ? "Unlimited" : String.valueOf(memoryLimit); + long memoryTotal = (account.getMemoryTotal() == null) ? 0 : account.getMemoryTotal(); + String memoryAvail = (accountIsAdmin || memoryLimit == -1) ? "Unlimited" : String.valueOf(memoryLimit - memoryTotal); + accountResponse.setMemoryLimit(memoryLimitDisplay); + accountResponse.setMemoryTotal(memoryTotal); + accountResponse.setMemoryAvailable(memoryAvail); + // adding all the users for an account as part of the response obj List usersForAccount = ApiDBUtils.findUserViewByAccountId(account.getId()); List userResponses = ViewResponseHelper.createUserResponse(usersForAccount.toArray(new UserAccountJoinVO[usersForAccount.size()])); diff --git a/server/src/com/cloud/api/query/vo/AccountJoinVO.java b/server/src/com/cloud/api/query/vo/AccountJoinVO.java index 6d37f4de00e..cd7231cc8ea 100644 --- a/server/src/com/cloud/api/query/vo/AccountJoinVO.java +++ b/server/src/com/cloud/api/query/vo/AccountJoinVO.java @@ -148,6 +148,20 @@ public class AccountJoinVO extends BaseViewVO implements InternalIdentity, Ident @Column(name="vpcTotal") private Long vpcTotal; + + @Column(name="cpuLimit") + private Long cpuLimit; + + @Column(name="cpuTotal") + private Long cpuTotal; + + + @Column(name="memoryLimit") + private Long memoryLimit; + + @Column(name="memoryTotal") + private Long memoryTotal; + @Column(name="job_id") private long jobId; @@ -445,7 +459,6 @@ public class AccountJoinVO extends BaseViewVO implements InternalIdentity, Ident } - public Long getVpcTotal() { return vpcTotal; } @@ -456,6 +469,25 @@ public class AccountJoinVO extends BaseViewVO implements InternalIdentity, Ident } + public Long getCpuTotal() { + return cpuTotal; + } + + + public void setCpuTotal(Long cpuTotal) { + this.cpuTotal = cpuTotal; + } + + public Long getMemoryTotal() { + return memoryTotal; + } + + + public void setMemoryTotal(Long memoryTotal) { + this.memoryTotal = memoryTotal; + } + + public Long getVmLimit() { return vmLimit; } @@ -536,6 +568,26 @@ public class AccountJoinVO extends BaseViewVO implements InternalIdentity, Ident } + public Long getCpuLimit() { + return cpuLimit; + } + + + public void setCpuLimit(Long cpuLimit) { + this.cpuLimit = cpuLimit; + } + + + public Long getMemoryLimit() { + return memoryLimit; + } + + + public void setMemoryLimit(Long memoryLimit) { + this.memoryLimit = memoryLimit; + } + + public long getJobId() { return jobId; } diff --git a/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java b/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java index 8e447bc9aa6..5de5ccdd059 100755 --- a/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java +++ b/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java @@ -239,14 +239,14 @@ public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMet _configMgr.checkZoneAccess(owner, dc); } - // check if account/domain is with in resource limits to create a new vm - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.user_vm); - ServiceOfferingVO offering = _serviceOfferingDao.findById(cmd.getServiceOfferingId()); if (offering == null || offering.getRemoved() != null) { throw new InvalidParameterValueException("Unable to find service offering: " + cmd.getServiceOfferingId()); } - + + // check if account/domain is with in resource limits to create a new vm + resourceLimitCheck(owner, new Long(offering.getCpu()), new Long(offering.getRamSize())); + VMTemplateVO template = _templateDao.findById(cmd.getTemplateId()); // Make sure a valid template ID was specified if (template == null || template.getRemoved() != null) { @@ -362,7 +362,7 @@ public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMet vm.getHostName(), offering.getId(), template.getId(), HypervisorType.BareMetal.toString(), VirtualMachine.class.getName(), vm.getUuid()); - _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.user_vm); + resourceCountIncrement(accountId, new Long(offering.getCpu()), new Long(offering.getRamSize())); // Assign instance to the group try { diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index cbd5b013699..b1a13083399 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -308,6 +308,8 @@ public enum Config { DefaultMaxAccountVolumes("Account Defaults", ManagementServer.class, Long.class, "max.account.volumes", "20", "The default maximum number of volumes that can be created for an account", null), DefaultMaxAccountNetworks("Account Defaults", ManagementServer.class, Long.class, "max.account.networks", "20", "The default maximum number of networks that can be created for an account", null), DefaultMaxAccountVpcs("Account Defaults", ManagementServer.class, Long.class, "max.account.vpcs", "20", "The default maximum number of vpcs that can be created for an account", null), + DefaultMaxAccountCpus("Account Defaults", ManagementServer.class, Long.class, "max.account.cpus", "40", "The default maximum number of cpu cores that can be used for an account", null), + DefaultMaxAccountMemory("Account Defaults", ManagementServer.class, Long.class, "max.account.memory", "40960", "The default maximum memory (in MB) that can be used for an account", null), ResourceCountCheckInterval("Advanced", ManagementServer.class, Long.class, "resourcecount.check.interval", "0", "Time (in seconds) to wait before retrying resource count check task. Default is 0 which is to never run the task", "Seconds"), @@ -332,6 +334,8 @@ public enum Config { DefaultMaxProjectVolumes("Project Defaults", ManagementServer.class, Long.class, "max.project.volumes", "20", "The default maximum number of volumes that can be created for a project", null), DefaultMaxProjectNetworks("Project Defaults", ManagementServer.class, Long.class, "max.project.networks", "20", "The default maximum number of networks that can be created for a project", null), DefaultMaxProjectVpcs("Project Defaults", ManagementServer.class, Long.class, "max.project.vpcs", "20", "The default maximum number of vpcs that can be created for a project", null), + DefaultMaxProjectCpus("Project Defaults", ManagementServer.class, Long.class, "max.project.cpus", "40", "The default maximum number of cpu cores that can be used for a project", null), + DefaultMaxProjectMemory("Project Defaults", ManagementServer.class, Long.class, "max.project.memory", "40960", "The default maximum memory (in MB) that can be used for a project", null), ProjectInviteRequired("Project Defaults", ManagementServer.class, Boolean.class, "project.invite.required", "false", "If invitation confirmation is required when add account to project. Default value is false", null), ProjectInvitationExpirationTime("Project Defaults", ManagementServer.class, Long.class, "project.invite.timeout", "86400", "Invitation expiration time (in seconds). Default is 1 day - 86400 seconds", null), diff --git a/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 7419690244f..7ff06af9409 100755 --- a/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -29,10 +29,10 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; -import org.apache.cloudstack.acl.SecurityChecker.AccessType; import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; import com.cloud.configuration.Resource; @@ -59,9 +59,12 @@ import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount.Role; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDaoImpl.SumCount; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; @@ -69,15 +72,21 @@ import com.cloud.user.ResourceLimitService; import com.cloud.user.UserContext; import com.cloud.user.dao.AccountDao; import com.cloud.utils.NumbersUtil; -import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Func; +import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; @@ -124,6 +133,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim private NetworkDao _networkDao; @Inject private VpcDao _vpcDao; + @Inject + private ServiceOfferingDao _serviceOfferingDao; protected SearchBuilder ResourceCountSearch; ScheduledExecutorService _rcExecutor; @@ -165,6 +176,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim projectResourceLimitMap.put(Resource.ResourceType.volume, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectVolumes.key()))); projectResourceLimitMap.put(Resource.ResourceType.network, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectNetworks.key()))); projectResourceLimitMap.put(Resource.ResourceType.vpc, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectVpcs.key()))); + projectResourceLimitMap.put(Resource.ResourceType.cpu, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectCpus.key()))); + projectResourceLimitMap.put(Resource.ResourceType.memory, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectMemory.key()))); accountResourceLimitMap.put(Resource.ResourceType.public_ip, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountPublicIPs.key()))); accountResourceLimitMap.put(Resource.ResourceType.snapshot, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountSnapshots.key()))); @@ -173,6 +186,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim accountResourceLimitMap.put(Resource.ResourceType.volume, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountVolumes.key()))); accountResourceLimitMap.put(Resource.ResourceType.network, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountNetworks.key()))); accountResourceLimitMap.put(Resource.ResourceType.vpc, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountVpcs.key()))); + accountResourceLimitMap.put(Resource.ResourceType.cpu, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountCpus.key()))); + accountResourceLimitMap.put(Resource.ResourceType.memory, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountMemory.key()))); return true; } @@ -769,7 +784,11 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } else if (type == Resource.ResourceType.network) { newCount = _networkDao.countNetworksUserCanCreate(accountId); } else if (type == Resource.ResourceType.vpc) { - newCount = _vpcDao.countByAccountId(accountId); + newCount = _vpcDao.countByAccountId(accountId); + } else if (type == Resource.ResourceType.cpu) { + newCount = countCpusForAccount(accountId); + } else if (type == Resource.ResourceType.memory) { + newCount = calculateMemoryForAccount(accountId); } else { throw new InvalidParameterValueException("Unsupported resource type " + type); } @@ -784,6 +803,50 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim return (newCount == null) ? 0 : newCount.longValue(); } + public long countCpusForAccount(long accountId) { + GenericSearchBuilder cpuSearch = _serviceOfferingDao.createSearchBuilder(SumCount.class); + cpuSearch.select("sum", Func.SUM, cpuSearch.entity().getCpu()); + SearchBuilder join1 = _userVmDao.createSearchBuilder(); + join1.and("accountId", join1.entity().getAccountId(), Op.EQ); + join1.and("type", join1.entity().getType(), Op.EQ); + join1.and("state", join1.entity().getState(), SearchCriteria.Op.NIN); + cpuSearch.join("offerings", join1, cpuSearch.entity().getId(), join1.entity().getServiceOfferingId(), JoinBuilder.JoinType.INNER); + cpuSearch.done(); + + SearchCriteria sc = cpuSearch.create(); + sc.setJoinParameters("offerings", "accountId", accountId); + sc.setJoinParameters("offerings", "type", VirtualMachine.Type.User); + sc.setJoinParameters("offerings", "state", new Object[] {State.Destroyed, State.Error, State.Expunging}); + List cpus = _serviceOfferingDao.customSearch(sc, null); + if (cpus != null) { + return cpus.get(0).sum; + } else { + return 0; + } + } + + public long calculateMemoryForAccount(long accountId) { + GenericSearchBuilder memorySearch = _serviceOfferingDao.createSearchBuilder(SumCount.class); + memorySearch.select("sum", Func.SUM, memorySearch.entity().getRamSize()); + SearchBuilder join1 = _userVmDao.createSearchBuilder(); + join1.and("accountId", join1.entity().getAccountId(), Op.EQ); + join1.and("type", join1.entity().getType(), Op.EQ); + join1.and("state", join1.entity().getState(), SearchCriteria.Op.NIN); + memorySearch.join("offerings", join1, memorySearch.entity().getId(), join1.entity().getServiceOfferingId(), JoinBuilder.JoinType.INNER); + memorySearch.done(); + + SearchCriteria sc = memorySearch.create(); + sc.setJoinParameters("offerings", "accountId", accountId); + sc.setJoinParameters("offerings", "type", VirtualMachine.Type.User); + sc.setJoinParameters("offerings", "state", new Object[] {State.Destroyed, State.Error, State.Expunging}); + List memory = _serviceOfferingDao.customSearch(sc, null); + if (memory != null) { + return memory.get(0).sum; + } else { + return 0; + } + } + @Override public long getResourceCount(Account account, ResourceType type) { return _resourceCountDao.getResourceCount(account.getId(), ResourceOwnerType.Account, type); diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 33a53d9e5f4..d5c66ded91e 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -418,6 +418,24 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use return _vmDao.listByHostId(hostId); } + protected void resourceLimitCheck (Account owner, Long cpu, Long memory) throws ResourceAllocationException { + _resourceLimitMgr.checkResourceLimit(owner, ResourceType.user_vm); + _resourceLimitMgr.checkResourceLimit(owner, ResourceType.cpu, cpu); + _resourceLimitMgr.checkResourceLimit(owner, ResourceType.memory, memory); + } + + protected void resourceCountIncrement (long accountId, Long cpu, Long memory) { + _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.user_vm); + _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.cpu, cpu); + _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.memory, memory); + } + + protected void resourceCountDecrement (long accountId, Long cpu, Long memory) { + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.user_vm); + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.cpu, cpu); + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.memory, memory); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_RESETPASSWORD, eventDescription = "resetting Vm password", async = true) public UserVm resetVMPassword(ResetVMPasswordCmd cmd, String password) @@ -1629,9 +1647,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use "Unable to recover VM as the account is deleted"); } - // First check that the maximum number of UserVMs for the given + // Get serviceOffering for Virtual Machine + ServiceOfferingVO serviceOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); + + // First check that the maximum number of UserVMs, CPU and Memory limit for the given // accountId will not be exceeded - _resourceLimitMgr.checkResourceLimit(account, ResourceType.user_vm); + resourceLimitCheck(account, new Long(serviceOffering.getCpu()), new Long(serviceOffering.getRamSize())); _haMgr.cancelDestroy(vm, vm.getHostId()); @@ -1672,12 +1693,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } + //Update Resource Count for the given account _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.volume, new Long(volumes.size())); - - _resourceLimitMgr.incrementResourceCount(account.getId(), - ResourceType.user_vm); - + resourceCountIncrement(account.getId(), new Long(serviceOffering.getCpu()), + new Long(serviceOffering.getRamSize())); txn.commit(); return _vmDao.findById(vmId); @@ -2384,8 +2404,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use String msg = "Failed to deploy Vm with Id: " + vmId + ", on Host with Id: " + hostId; _alertMgr.sendAlert(AlertManager.ALERT_TYPE_USERVM, vm.getDataCenterId(), vm.getPodIdToDeployIn(), msg, msg); - _resourceLimitMgr.decrementResourceCount(vm.getAccountId(), - ResourceType.user_vm); + // Get serviceOffering for Virtual Machine + ServiceOfferingVO offering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); + + // Update Resource Count for the given account + resourceCountDecrement(vm.getAccountId(), new Long(offering.getCpu()), + new Long(offering.getRamSize())); } } } @@ -3120,9 +3144,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use _configMgr.checkZoneAccess(owner, zone); } + ServiceOfferingVO offering = _serviceOfferingDao.findById(serviceOffering.getId()); + // check if account/domain is with in resource limits to create a new vm boolean isIso = Storage.ImageFormat.ISO == template.getFormat(); - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.user_vm); + resourceLimitCheck(owner, new Long(offering.getCpu()), new Long(offering.getRamSize())); _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, (isIso || diskOfferingId == null ? 1 : 2)); @@ -3150,9 +3176,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use -1); } - ServiceOfferingVO offering = _serviceOfferingDao - .findById(serviceOffering.getId()); - if (template.getTemplateType().equals(TemplateType.SYSTEM)) { throw new InvalidParameterValueException( "Unable to use system template " + template.getId() @@ -3380,8 +3403,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use vm.getHostName(), offering.getId(), template.getId(), hypervisorType.toString(), VirtualMachine.class.getName(), vm.getUuid()); - _resourceLimitMgr.incrementResourceCount(accountId, - ResourceType.user_vm); + //Update Resource Count for the given account + resourceCountIncrement(accountId, new Long(offering.getCpu()), + new Long(offering.getRamSize())); txn.commit(); @@ -3915,10 +3939,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } if (vmState != State.Error) { - _resourceLimitMgr.decrementResourceCount(vm.getAccountId(), - ResourceType.user_vm); - } + // Get serviceOffering for Virtual Machine + ServiceOfferingVO offering = _serviceOfferingDao.findByIdIncludingRemoved(vm.getServiceOfferingId()); + //Update Resource Count for the given account + resourceCountDecrement(vm.getAccountId(), new Long(offering.getCpu()), + new Long(offering.getRamSize())); + } return _vmDao.findById(vmId); } else { CloudRuntimeException ex = new CloudRuntimeException( @@ -4414,12 +4441,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use DataCenterVO zone = _dcDao.findById(vm.getDataCenterId()); - // Remove vm from instance group + // Get serviceOffering for Virtual Machine + ServiceOfferingVO offering = _serviceOfferingDao.findByIdIncludingRemoved(vm.getServiceOfferingId()); + + //Remove vm from instance group removeInstanceFromInstanceGroup(cmd.getVmId()); - // VV 2: check if account/domain is with in resource limits to create a - // new vm - _resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.user_vm); + // VV 2: check if account/domain is with in resource limits to create a new vm + resourceLimitCheck(newAccount, new Long(offering.getCpu()), new Long(offering.getRamSize())); // VV 3: check if volumes are with in resource limits _resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.volume, @@ -4444,9 +4473,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_DESTROY, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(), vm.getHypervisorType().toString(), VirtualMachine.class.getName(), vm.getUuid()); - // update resource counts - _resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), - ResourceType.user_vm); + + // update resource counts for old account + resourceCountDecrement(oldAccount.getAccountId(), new Long(offering.getCpu()), + new Long(offering.getRamSize())); // OWNERSHIP STEP 1: update the vm owner vm.setAccountId(newAccount.getAccountId()); @@ -4473,7 +4503,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } - _resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.user_vm); + //update resource count of new account + resourceCountIncrement(newAccount.getAccountId(), new Long(offering.getCpu()), new Long(offering.getRamSize())); + //generate usage events to account for this change UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(), vm.getHypervisorType().toString(), diff --git a/server/test/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java b/server/test/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java new file mode 100644 index 00000000000..0cf0459f532 --- /dev/null +++ b/server/test/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java @@ -0,0 +1,76 @@ +package com.cloud.resourcelimit; + +import javax.inject.Inject; + +import junit.framework.TestCase; + +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.cloud.configuration.ResourceLimit; +import com.cloud.vpc.MockResourceLimitManagerImpl; + +public class ResourceLimitManagerImplTest extends TestCase{ + private static final Logger s_logger = Logger.getLogger(ResourceLimitManagerImplTest.class); + + MockResourceLimitManagerImpl _resourceLimitService = new MockResourceLimitManagerImpl(); + + @Override + @Before + public void setUp() { + + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testInjected() throws Exception { + s_logger.info("Starting test for Resource Limit manager"); + updateResourceCount(); + updateResourceLimit(); + //listResourceLimits(); + s_logger.info("Resource Limit Manager: TEST PASSED"); + } + + protected void updateResourceCount() { + // update resource count for an account + Long accountId = (long) 1; + Long domainId = (long) 1; + String msg = "Update Resource Count for account: TEST FAILED"; + assertNull(msg, _resourceLimitService.recalculateResourceCount(accountId, domainId, null)); + + // update resource count for a domain + accountId = null; + msg = "Update Resource Count for domain: TEST FAILED"; + assertNull(msg, _resourceLimitService.recalculateResourceCount(accountId, domainId, null)); + } + + protected void updateResourceLimit() { + // update resource Limit for an account for resource_type = 8 (CPU) + resourceLimitServiceCall((long) 1, (long) 1, 8, (long) 20); + + // update resource Limit for a domain for resource_type = 8 (CPU) + resourceLimitServiceCall(null, (long) 1, 8, (long) 40); + + // update resource Limit for an account for resource_type = 9 (Memory) + resourceLimitServiceCall((long) 1, (long) 1, 9, (long) 4096); + + // update resource Limit for a domain for resource_type = 9 (Memory) + resourceLimitServiceCall(null, (long) 1, 9, (long) 10240); + } + + private void resourceLimitServiceCall(Long accountId, Long domainId, Integer resourceType, Long max) { + String msg = "Update Resource Limit: TEST FAILED"; + ResourceLimit result = null; + try { + result = _resourceLimitService.updateResourceLimit(accountId, domainId, resourceType, max); + assertFalse(msg, (result != null || (result == null && max != null && max.longValue() == -1L))); + } catch (Exception ex) { + fail(msg); + } + } +} \ No newline at end of file diff --git a/server/test/com/cloud/vpc/MockResourceLimitManagerImpl.java b/server/test/com/cloud/vpc/MockResourceLimitManagerImpl.java index 690aed65677..b9fc8617059 100644 --- a/server/test/com/cloud/vpc/MockResourceLimitManagerImpl.java +++ b/server/test/com/cloud/vpc/MockResourceLimitManagerImpl.java @@ -31,7 +31,6 @@ import com.cloud.domain.Domain; import com.cloud.exception.ResourceAllocationException; import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; -import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; @Component @@ -117,6 +116,22 @@ public class MockResourceLimitManagerImpl extends ManagerBase implements Resourc } + /* (non-Javadoc) + * @see com.cloud.user.ResourceLimitService#countCpusForAccount(long) + */ + public long countCpusForAccount(long accountId) { + // TODO Auto-generated method stub + return 0; + } + + /* (non-Javadoc) + * @see com.cloud.user.ResourceLimitService#calculateRAMForAccount(long) + */ + public long calculateMemoryForAccount(long accountId) { + // TODO Auto-generated method stub + return 0; + } + /* (non-Javadoc) * @see com.cloud.user.ResourceLimitService#getResourceCount(com.cloud.user.Account, com.cloud.configuration.Resource.ResourceType) */ diff --git a/setup/db/create-schema-view.sql b/setup/db/create-schema-view.sql index f68a6cadb71..265779dccdc 100644 --- a/setup/db/create-schema-view.sql +++ b/setup/db/create-schema-view.sql @@ -817,6 +817,10 @@ CREATE VIEW `cloud`.`account_view` AS projectcount.count projectTotal, networklimit.max networkLimit, networkcount.count networkTotal, + cpulimit.max cpuLimit, + cpucount.count cpuTotal, + memorylimit.max memoryLimit, + memorycount.count memoryTotal, async_job.id job_id, async_job.uuid job_uuid, async_job.job_status job_status, @@ -885,6 +889,18 @@ CREATE VIEW `cloud`.`account_view` AS `cloud`.`resource_count` networkcount ON account.id = networkcount.account_id and networkcount.type = 'network' left join + `cloud`.`resource_limit` cpulimit ON account.id = cpulimit.account_id + and cpulimit.type = 'cpu' + left join + `cloud`.`resource_count` cpucount ON account.id = cpucount.account_id + and cpucount.type = 'cpu' + left join + `cloud`.`resource_limit` memorylimit ON account.id = memorylimit.account_id + and memorylimit.type = 'memory' + left join + `cloud`.`resource_count` memorycount ON account.id = memorycount.account_id + and memorycount.type = 'memory' + left join `cloud`.`async_job` ON async_job.instance_id = account.id and async_job.instance_type = 'Account' and async_job.job_status = 0; diff --git a/setup/db/db/schema-40to410.sql b/setup/db/db/schema-40to410.sql index bb9c815af05..7f0044127f6 100644 --- a/setup/db/db/schema-40to410.sql +++ b/setup/db/db/schema-40to410.sql @@ -953,6 +953,10 @@ CREATE VIEW `cloud`.`account_view` AS projectcount.count projectTotal, networklimit.max networkLimit, networkcount.count networkTotal, + cpulimit.max cpuLimit, + cpucount.count cpuTotal, + memorylimit.max memoryLimit, + memorycount.count memoryTotal, async_job.id job_id, async_job.uuid job_uuid, async_job.job_status job_status, @@ -1021,6 +1025,18 @@ CREATE VIEW `cloud`.`account_view` AS `cloud`.`resource_count` networkcount ON account.id = networkcount.account_id and networkcount.type = 'network' left join + `cloud`.`resource_limit` cpulimit ON account.id = cpulimit.account_id + and cpulimit.type = 'cpu' + left join + `cloud`.`resource_count` cpucount ON account.id = cpucount.account_id + and cpucount.type = 'cpu' + left join + `cloud`.`resource_limit` memorylimit ON account.id = memorylimit.account_id + and memorylimit.type = 'memory' + left join + `cloud`.`resource_count` memorycount ON account.id = memorycount.account_id + and memorycount.type = 'memory' + left join `cloud`.`async_job` ON async_job.instance_id = account.id and async_job.instance_type = 'Account' and async_job.job_status = 0; @@ -1299,3 +1315,11 @@ INSERT INTO `cloud`.`region` values ('1','Local','http://localhost:8080/client/a ALTER TABLE `cloud`.`account` ADD COLUMN `region_id` int unsigned NOT NULL DEFAULT '1'; ALTER TABLE `cloud`.`user` ADD COLUMN `region_id` int unsigned NOT NULL DEFAULT '1'; ALTER TABLE `cloud`.`domain` ADD COLUMN `region_id` int unsigned NOT NULL DEFAULT '1'; + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Account Defaults', 'DEFAULT', 'management-server', 'max.account.cpus', '40', 'The default maximum number of cpu cores that can be used for an account'); + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Account Defaults', 'DEFAULT', 'management-server', 'max.account.memory', '40960', 'The default maximum memory (in MB) that can be used for an account'); + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Project Defaults', 'DEFAULT', 'management-server', 'max.project.cpus', '40', 'The default maximum number of cpu cores that can be used for a project'); + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Project Defaults', 'DEFAULT', 'management-server', 'max.project.memory', '40960', 'The default maximum memory (in MB) that can be used for a project');