diff --git a/api/src/com/cloud/async/AsyncJob.java b/api/src/com/cloud/async/AsyncJob.java index 152880930cb..386d4efe40c 100644 --- a/api/src/com/cloud/async/AsyncJob.java +++ b/api/src/com/cloud/async/AsyncJob.java @@ -50,7 +50,8 @@ public interface AsyncJob extends Identity, InternalIdentity { AutoScaleVmProfile, AutoScaleVmGroup, GlobalLoadBalancerRule, - LoadBalancerRule + LoadBalancerRule, + AffinityGroup } long getUserId(); diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 5671f995c70..6a26212ecc7 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -348,7 +348,7 @@ public class EventTypes { // tag related events public static final String EVENT_TAGS_CREATE = "CREATE_TAGS"; public static final String EVENT_TAGS_DELETE = "DELETE_TAGS"; - + // vm snapshot events public static final String EVENT_VM_SNAPSHOT_CREATE = "VMSNAPSHOT.CREATE"; public static final String EVENT_VM_SNAPSHOT_DELETE = "VMSNAPSHOT.DELETE"; @@ -382,6 +382,12 @@ public class EventTypes { public static final String EVENT_BAREMETAL_PXE_SERVER_ADD = "PHYSICAL.PXE.ADD"; public static final String EVENT_BAREMETAL_PXE_SERVER_DELETE = "PHYSICAL.PXE.DELETE"; + public static final String EVENT_AFFINITY_GROUP_CREATE = "AG.CREATE"; + public static final String EVENT_AFFINITY_GROUP_DELETE = "AG.DELETE"; + public static final String EVENT_AFFINITY_GROUP_ASSIGN = "AG.ASSIGN"; + public static final String EVENT_AFFINITY_GROUP_REMOVE = "AG.REMOVE"; + public static final String EVENT_VM_AFFINITY_GROUP_UPDATE = "VM.AG.UPDATE"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking diff --git a/api/src/com/cloud/exception/AffinityConflictException.java b/api/src/com/cloud/exception/AffinityConflictException.java new file mode 100644 index 00000000000..8b187783f24 --- /dev/null +++ b/api/src/com/cloud/exception/AffinityConflictException.java @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.exception; + +import com.cloud.utils.SerialVersionUID; +import com.cloud.utils.exception.CloudRuntimeException; + +public class AffinityConflictException extends CloudRuntimeException { + + private static final long serialVersionUID = SerialVersionUID.AffinityConflictException; + + public AffinityConflictException(String message) { + super(message); + } + + public AffinityConflictException(String message, Throwable th) { + super(message, th); + } + +} diff --git a/api/src/com/cloud/server/ManagementService.java b/api/src/com/cloud/server/ManagementService.java index 1e6ca8d0b67..6e6dbc36f0c 100755 --- a/api/src/com/cloud/server/ManagementService.java +++ b/api/src/com/cloud/server/ManagementService.java @@ -123,7 +123,7 @@ public interface ManagementService { /** * Searches for servers by the specified search criteria Can search by: "name", "type", "state", "dataCenterId", * "podId" - * + * * @param cmd * @return List of Hosts */ @@ -407,4 +407,6 @@ public interface ManagementService { */ List listTopConsumedResources(ListCapacityCmd cmd); + List listDeploymentPlanners(); + } diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index 2c33d41cd35..d963b74b080 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -26,9 +26,6 @@ import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.*; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; -import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; - import com.cloud.dc.DataCenter; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -42,7 +39,6 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network.IpAddresses; import com.cloud.offering.ServiceOffering; import com.cloud.storage.StoragePool; -import com.cloud.storage.Volume; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.uservm.UserVm; @@ -104,14 +100,14 @@ public interface UserVmService { * @return the vm object if successful, null otherwise */ UserVm addNicToVirtualMachine(AddNicToVMCmd cmd); - + /** * Removes a NIC on the given network from the virtual machine * @param cmd the command object that defines the vm and the given network * @return the vm object if successful, null otherwise */ UserVm removeNicFromVirtualMachine(RemoveNicFromVMCmd cmd); - + /** * Updates default Nic to the given network for given virtual machine * @param cmd the command object that defines the vm and the given network @@ -123,7 +119,8 @@ public interface UserVmService { /** - * Creates a Basic Zone User VM in the database and returns the VM to the caller. + * Creates a Basic Zone User VM in the database and returns the VM to the + * caller. * * @param zone * - availability zone for the virtual machine @@ -132,61 +129,69 @@ public interface UserVmService { * @param template * - the template for the virtual machine * @param securityGroupIdList - * - comma separated list of security groups id that going to be applied to the virtual machine + * - comma separated list of security groups id that going to be + * applied to the virtual machine * @param hostName * - host name for the virtual machine * @param displayName * - an optional user generated name for the virtual machine * @param diskOfferingId - * - the ID of the disk offering for the virtual machine. If the template is of ISO format, the - * diskOfferingId is - * for the root disk volume. Otherwise this parameter is used to indicate the offering for the data disk - * volume. - * If the templateId parameter passed is from a Template object, the diskOfferingId refers to a DATA Disk - * Volume - * created. If the templateId parameter passed is from an ISO object, the diskOfferingId refers to a ROOT - * Disk - * Volume created + * - the ID of the disk offering for the virtual machine. If the + * template is of ISO format, the diskOfferingId is for the root + * disk volume. Otherwise this parameter is used to indicate the + * offering for the data disk volume. If the templateId parameter + * passed is from a Template object, the diskOfferingId refers to + * a DATA Disk Volume created. If the templateId parameter passed + * is from an ISO object, the diskOfferingId refers to a ROOT + * Disk Volume created * @param diskSize - * - the arbitrary size for the DATADISK volume. Mutually exclusive with diskOfferingId + * - the arbitrary size for the DATADISK volume. Mutually + * exclusive with diskOfferingId * @param group * - an optional group for the virtual machine * @param hypervisor * - the hypervisor on which to deploy the virtual machine * @param userData - * - an optional binary data that can be sent to the virtual machine upon a successful deployment. This - * binary - * data must be base64 encoded before adding it to the request. Currently only HTTP GET is supported. - * Using HTTP - * GET (via querystring), you can send up to 2KB of data after base64 encoding + * - an optional binary data that can be sent to the virtual + * machine upon a successful deployment. This binary data must be + * base64 encoded before adding it to the request. Currently only + * HTTP GET is supported. Using HTTP GET (via querystring), you + * can send up to 2KB of data after base64 encoding * @param sshKeyPair - * - name of the ssh key pair used to login to the virtual machine + * - name of the ssh key pair used to login to the virtual + * machine * @param requestedIps * TODO * @param defaultIp * TODO + * @param affinityGroupIdList * @param accountName - * - an optional account for the virtual machine. Must be used with domainId + * - an optional account for the virtual machine. Must be used + * with domainId * @param domainId - * - an optional domainId for the virtual machine. If the account parameter is used, domainId must also - * be used + * - an optional domainId for the virtual machine. If the account + * parameter is used, domainId must also be used * @return UserVm object if successful. * * @throws InsufficientCapacityException * if there is insufficient capacity to deploy the VM. * @throws ConcurrentOperationException - * if there are multiple users working on the same VM or in the same environment. + * if there are multiple users working on the same VM or in the + * same environment. * @throws ResourceUnavailableException - * if the resources required to deploy the VM is not currently available. + * if the resources required to deploy the VM is not currently + * available. * @throws InsufficientResourcesException */ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, - String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIp, String keyboard) + String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIp, + String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** - * Creates a User VM in Advanced Zone (Security Group feature is enabled) in the database and returns the VM to the - * caller. + * Creates a User VM in Advanced Zone (Security Group feature is enabled) in + * the database and returns the VM to the caller. * * @param zone * - availability zone for the virtual machine @@ -197,63 +202,69 @@ public interface UserVmService { * @param networkIdList * - list of network ids used by virtual machine * @param securityGroupIdList - * - comma separated list of security groups id that going to be applied to the virtual machine + * - comma separated list of security groups id that going to be + * applied to the virtual machine * @param hostName * - host name for the virtual machine * @param displayName * - an optional user generated name for the virtual machine * @param diskOfferingId - * - the ID of the disk offering for the virtual machine. If the template is of ISO format, the - * diskOfferingId is - * for the root disk volume. Otherwise this parameter is used to indicate the offering for the data disk - * volume. - * If the templateId parameter passed is from a Template object, the diskOfferingId refers to a DATA Disk - * Volume - * created. If the templateId parameter passed is from an ISO object, the diskOfferingId refers to a ROOT - * Disk - * Volume created + * - the ID of the disk offering for the virtual machine. If the + * template is of ISO format, the diskOfferingId is for the root + * disk volume. Otherwise this parameter is used to indicate the + * offering for the data disk volume. If the templateId parameter + * passed is from a Template object, the diskOfferingId refers to + * a DATA Disk Volume created. If the templateId parameter passed + * is from an ISO object, the diskOfferingId refers to a ROOT + * Disk Volume created * @param diskSize - * - the arbitrary size for the DATADISK volume. Mutually exclusive with diskOfferingId + * - the arbitrary size for the DATADISK volume. Mutually + * exclusive with diskOfferingId * @param group * - an optional group for the virtual machine * @param hypervisor * - the hypervisor on which to deploy the virtual machine * @param userData - * - an optional binary data that can be sent to the virtual machine upon a successful deployment. This - * binary - * data must be base64 encoded before adding it to the request. Currently only HTTP GET is supported. - * Using HTTP - * GET (via querystring), you can send up to 2KB of data after base64 encoding + * - an optional binary data that can be sent to the virtual + * machine upon a successful deployment. This binary data must be + * base64 encoded before adding it to the request. Currently only + * HTTP GET is supported. Using HTTP GET (via querystring), you + * can send up to 2KB of data after base64 encoding * @param sshKeyPair - * - name of the ssh key pair used to login to the virtual machine + * - name of the ssh key pair used to login to the virtual + * machine * @param requestedIps * TODO * @param defaultIps * TODO + * @param affinityGroupIdList * @param accountName - * - an optional account for the virtual machine. Must be used with domainId + * - an optional account for the virtual machine. Must be used + * with domainId * @param domainId - * - an optional domainId for the virtual machine. If the account parameter is used, domainId must also - * be used + * - an optional domainId for the virtual machine. If the account + * parameter is used, domainId must also be used * @return UserVm object if successful. * * @throws InsufficientCapacityException * if there is insufficient capacity to deploy the VM. * @throws ConcurrentOperationException - * if there are multiple users working on the same VM or in the same environment. + * if there are multiple users working on the same VM or in the + * same environment. * @throws ResourceUnavailableException - * if the resources required to deploy the VM is not currently available. + * if the resources required to deploy the VM is not currently + * available. * @throws InsufficientResourcesException */ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, - IpAddresses defaultIps, String keyboard) + IpAddresses defaultIps, String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** - * Creates a User VM in Advanced Zone (Security Group feature is disabled) in the database and returns the VM to the - * caller. - * + * Creates a User VM in Advanced Zone (Security Group feature is disabled) + * in the database and returns the VM to the caller. + * * @param zone * - availability zone for the virtual machine * @param serviceOffering @@ -267,49 +278,57 @@ public interface UserVmService { * @param displayName * - an optional user generated name for the virtual machine * @param diskOfferingId - * - the ID of the disk offering for the virtual machine. If the template is of ISO format, the - * diskOfferingId is - * for the root disk volume. Otherwise this parameter is used to indicate the offering for the data disk - * volume. - * If the templateId parameter passed is from a Template object, the diskOfferingId refers to a DATA Disk - * Volume - * created. If the templateId parameter passed is from an ISO object, the diskOfferingId refers to a ROOT - * Disk - * Volume created + * - the ID of the disk offering for the virtual machine. If the + * template is of ISO format, the diskOfferingId is for the root + * disk volume. Otherwise this parameter is used to indicate the + * offering for the data disk volume. If the templateId parameter + * passed is from a Template object, the diskOfferingId refers to + * a DATA Disk Volume created. If the templateId parameter passed + * is from an ISO object, the diskOfferingId refers to a ROOT + * Disk Volume created * @param diskSize - * - the arbitrary size for the DATADISK volume. Mutually exclusive with diskOfferingId + * - the arbitrary size for the DATADISK volume. Mutually + * exclusive with diskOfferingId * @param group * - an optional group for the virtual machine * @param hypervisor * - the hypervisor on which to deploy the virtual machine * @param userData - * - an optional binary data that can be sent to the virtual machine upon a successful deployment. This - * binary - * data must be base64 encoded before adding it to the request. Currently only HTTP GET is supported. - * Using HTTP - * GET (via querystring), you can send up to 2KB of data after base64 encoding + * - an optional binary data that can be sent to the virtual + * machine upon a successful deployment. This binary data must be + * base64 encoded before adding it to the request. Currently only + * HTTP GET is supported. Using HTTP GET (via querystring), you + * can send up to 2KB of data after base64 encoding * @param sshKeyPair - * - name of the ssh key pair used to login to the virtual machine + * - name of the ssh key pair used to login to the virtual + * machine * @param requestedIps * TODO - * @param defaultIps TODO + * @param defaultIps + * TODO + * @param affinityGroupIdList * @param accountName - * - an optional account for the virtual machine. Must be used with domainId + * - an optional account for the virtual machine. Must be used + * with domainId * @param domainId - * - an optional domainId for the virtual machine. If the account parameter is used, domainId must also - * be used + * - an optional domainId for the virtual machine. If the account + * parameter is used, domainId must also be used * @return UserVm object if successful. - * + * * @throws InsufficientCapacityException * if there is insufficient capacity to deploy the VM. * @throws ConcurrentOperationException - * if there are multiple users working on the same VM or in the same environment. + * if there are multiple users working on the same VM or in the + * same environment. * @throws ResourceUnavailableException - * if the resources required to deploy the VM is not currently available. + * if the resources required to deploy the VM is not currently + * available. * @throws InsufficientResourcesException */ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, - String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, String keyboard) + String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, + String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** diff --git a/api/src/org/apache/cloudstack/affinity/AffinityGroup.java b/api/src/org/apache/cloudstack/affinity/AffinityGroup.java new file mode 100644 index 00000000000..ac2eb613370 --- /dev/null +++ b/api/src/org/apache/cloudstack/affinity/AffinityGroup.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface AffinityGroup extends ControlledEntity, InternalIdentity, Identity { + + String getName(); + + String getDescription(); + + String getType(); + +} diff --git a/api/src/org/apache/cloudstack/affinity/AffinityGroupProcessor.java b/api/src/org/apache/cloudstack/affinity/AffinityGroupProcessor.java new file mode 100644 index 00000000000..60b8e4c554f --- /dev/null +++ b/api/src/org/apache/cloudstack/affinity/AffinityGroupProcessor.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; +import com.cloud.utils.component.Adapter; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +public interface AffinityGroupProcessor extends Adapter { + + /** + * process() is called to apply any user preferences to the deployment plan + * and avoid set for the given VM placement. + * + * @param vm + * virtual machine. + * @param plan + * deployment plan that tells you where it's being deployed to. + * @param avoid + * avoid these data centers, pods, clusters, or hosts. + */ + void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) + throws AffinityConflictException; + + /** + * getType() should return the affinity/anti-affinity group being + * implemented + * + * @return String Affinity/Anti-affinity type + */ + String getType(); +} \ No newline at end of file diff --git a/api/src/org/apache/cloudstack/affinity/AffinityGroupResponse.java b/api/src/org/apache/cloudstack/affinity/AffinityGroupResponse.java new file mode 100644 index 00000000000..afd33da84b7 --- /dev/null +++ b/api/src/org/apache/cloudstack/affinity/AffinityGroupResponse.java @@ -0,0 +1,155 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.api.response.ControlledEntityResponse; +import org.apache.cloudstack.api.response.ControlledViewEntityResponse; +import org.apache.cloudstack.api.response.UserVmResponse; + +import com.cloud.network.security.SecurityGroup; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@SuppressWarnings("unused") +@EntityReference(value = AffinityGroup.class) +public class AffinityGroupResponse extends BaseResponse implements ControlledViewEntityResponse { + + @SerializedName(ApiConstants.ID) @Param(description="the ID of the affinity group") + private String id; + + @SerializedName(ApiConstants.NAME) @Param(description="the name of the affinity group") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) @Param(description="the description of the affinity group") + private String description; + + @SerializedName(ApiConstants.ACCOUNT) @Param(description="the account owning the affinity group") + private String accountName; + + @SerializedName(ApiConstants.DOMAIN_ID) @Param(description="the domain ID of the affinity group") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) @Param(description="the domain name of the affinity group") + private String domainName; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "the type of the affinity group") + private String type; + + @SerializedName("virtualmachineIds") + @Param(description = "virtual machine Ids associated with this affinity group ") + private List vmIdList; + + public AffinityGroupResponse() { + this.vmIdList = new ArrayList(); + } + + @Override + public String getObjectId() { + return this.getId(); + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AffinityGroupResponse other = (AffinityGroupResponse) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + @Override + public void setProjectId(String projectId) { + // TODO Auto-generated method stub + + } + + @Override + public void setProjectName(String projectName) { + // TODO Auto-generated method stub + + } + + public void setVMIdList(List vmIdList) { + this.vmIdList = vmIdList; + } + + public void addVMId(String vmId) { + this.vmIdList.add(vmId); + } + +} diff --git a/api/src/org/apache/cloudstack/affinity/AffinityGroupService.java b/api/src/org/apache/cloudstack/affinity/AffinityGroupService.java new file mode 100644 index 00000000000..26c32c89c1f --- /dev/null +++ b/api/src/org/apache/cloudstack/affinity/AffinityGroupService.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.util.List; + +import com.cloud.exception.ResourceInUseException; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; + +public interface AffinityGroupService { + + /** + * Creates an affinity/anti-affinity group for the given account/domain. + * + * @param account + * @param domainId + * @param name + * @param type + * @param description + * @return AffinityGroup + */ + + AffinityGroup createAffinityGroup(String account, Long domainId, String affinityGroupName, + String affinityGroupType, String description); + + /** + * Creates an affinity/anti-affinity group. + * + * @param affinityGroupId + * @param account + * @param domainId + * @param affinityGroupName + * @throws ResourceInUseException + */ + boolean deleteAffinityGroup(Long affinityGroupId, String account, Long domainId, String affinityGroupName) + throws ResourceInUseException; + + /** Lists Affinity Groups in your account + * @param account + * @param domainId + * @param affinityGroupId + * @param affinityGroupName + * @param affinityGroupType + * @param vmId + * @param startIndex + * @param pageSize + * @return + */ + Pair, Integer> listAffinityGroups(Long affinityGroupId, String affinityGroupName, + String affinityGroupType, Long vmId, Long startIndex, Long pageSize); + + + /** + * List group types available in deployment + * + * @return + */ + List listAffinityGroupTypes(); + + AffinityGroup getAffinityGroup(Long groupId); + + UserVm updateVMAffinityGroups(Long vmId, List affinityGroupIds); + +} diff --git a/api/src/org/apache/cloudstack/affinity/AffinityGroupTypeResponse.java b/api/src/org/apache/cloudstack/affinity/AffinityGroupTypeResponse.java new file mode 100644 index 00000000000..2d1cd25edda --- /dev/null +++ b/api/src/org/apache/cloudstack/affinity/AffinityGroupTypeResponse.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.api.response.ControlledEntityResponse; +import org.apache.cloudstack.api.response.ControlledViewEntityResponse; + +import com.cloud.network.security.SecurityGroup; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@SuppressWarnings("unused") +@EntityReference(value = AffinityGroup.class) +public class AffinityGroupTypeResponse extends BaseResponse { + + @SerializedName(ApiConstants.TYPE) + @Param(description = "the type of the affinity group") + private String type; + + + public AffinityGroupTypeResponse() { + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/api/src/org/apache/cloudstack/affinity/AffinityProcessorBase.java b/api/src/org/apache/cloudstack/affinity/AffinityProcessorBase.java new file mode 100644 index 00000000000..70ecd08350f --- /dev/null +++ b/api/src/org/apache/cloudstack/affinity/AffinityProcessorBase.java @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +public class AffinityProcessorBase extends AdapterBase implements AffinityGroupProcessor { + + protected String _type; + + @Override + public void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) + throws AffinityConflictException { + + } + + @Override + public String getType() { + return _type; + } + + public void setType(String type) { + _type = type; + } +} diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 13bce079dcc..906630a9f56 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -479,13 +479,16 @@ public class ApiConstants { public static final String SOURCE_IP_NETWORK_ID = "sourceipaddressnetworkid"; public static final String SCHEME = "scheme"; public static final String PROVIDER_TYPE = "providertype"; + public static final String AFFINITY_GROUP_IDS = "affinitygroupids"; + public static final String AFFINITY_GROUP_NAMES = "affinitygroupnames"; + public static final String DEPLOYMENT_PLANNER = "deploymentplanner"; public enum HostDetails { all, capacity, events, stats, min; } public enum VMDetails { - all, group, nics, stats, secgrp, tmpl, servoff, iso, volume, min; + all, group, nics, stats, secgrp, tmpl, servoff, iso, volume, min, affgrp; } public enum LDAPParams { diff --git a/api/src/org/apache/cloudstack/api/BaseCmd.java b/api/src/org/apache/cloudstack/api/BaseCmd.java index 41f085ea061..9030d227fd6 100644 --- a/api/src/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/org/apache/cloudstack/api/BaseCmd.java @@ -27,6 +27,7 @@ import java.util.regex.Pattern; import javax.inject.Inject; +import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.usage.UsageService; @@ -137,6 +138,7 @@ public abstract class BaseCmd { @Inject public ApplicationLoadBalancerService _newLbSvc; @Inject public NetworkModel _ntwkModel; @Inject public ApplicationLoadBalancerService _appLbService; + @Inject public AffinityGroupService _affinityGroupService; public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException; @@ -159,7 +161,7 @@ public abstract class BaseCmd { /** * For commands the API framework needs to know the owner of the object being acted upon. This method is * used to determine that information. - * + * * @return the id of the account that owns the object being acted upon */ public abstract long getEntityOwnerId(); @@ -472,7 +474,7 @@ public abstract class BaseCmd { if (!enabledOnly || account.getState() == Account.State.enabled) { return account.getId(); } else { - throw new PermissionDeniedException("Can't add resources to the account id=" + account.getId() + " in state=" + account.getState() + " as it's no longer active"); + throw new PermissionDeniedException("Can't add resources to the account id=" + account.getId() + " in state=" + account.getState() + " as it's no longer active"); } } else { // idList is not used anywhere, so removed it now @@ -489,7 +491,7 @@ public abstract class BaseCmd { return project.getProjectAccountId(); } else { PermissionDeniedException ex = new PermissionDeniedException("Can't add resources to the project with specified projectId in state=" + project.getState() + " as it's no longer active"); - ex.addProxyObject(project, projectId, "projectId"); + ex.addProxyObject(project, projectId, "projectId"); throw ex; } } else { diff --git a/api/src/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/org/apache/cloudstack/api/ResponseGenerator.java index a17b079aebb..2fc14a467b8 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -21,10 +21,13 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; @@ -42,6 +45,7 @@ import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.ExtractResponse; import org.apache.cloudstack.api.response.FirewallResponse; import org.apache.cloudstack.api.response.FirewallRuleResponse; +import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; @@ -51,7 +55,6 @@ import org.apache.cloudstack.api.response.IpForwardingRuleResponse; import org.apache.cloudstack.api.response.LBHealthCheckResponse; import org.apache.cloudstack.api.response.LBStickinessResponse; import org.apache.cloudstack.api.response.LDAPConfigResponse; -import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.LoadBalancerResponse; import org.apache.cloudstack.api.response.NetworkACLResponse; import org.apache.cloudstack.api.response.NetworkOfferingResponse; @@ -118,9 +121,23 @@ import com.cloud.domain.Domain; import com.cloud.event.Event; import com.cloud.host.Host; import com.cloud.hypervisor.HypervisorCapabilities; -import com.cloud.network.*; +import com.cloud.network.IpAddress; +import com.cloud.network.Network; import com.cloud.network.Network.Service; -import com.cloud.network.as.*; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.PhysicalNetworkTrafficType; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.Site2SiteCustomerGateway; +import com.cloud.network.Site2SiteVpnConnection; +import com.cloud.network.Site2SiteVpnGateway; +import com.cloud.network.VirtualRouterProvider; +import com.cloud.network.VpnUser; +import com.cloud.network.as.AutoScalePolicy; +import com.cloud.network.as.AutoScaleVmGroup; +import com.cloud.network.as.AutoScaleVmProfile; +import com.cloud.network.as.Condition; +import com.cloud.network.as.Counter; import com.cloud.network.router.VirtualRouter; import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.HealthCheckPolicy; @@ -143,7 +160,12 @@ import com.cloud.projects.ProjectAccount; import com.cloud.projects.ProjectInvitation; import com.cloud.region.ha.GlobalLoadBalancerRule; import com.cloud.server.ResourceTag; -import com.cloud.storage.*; +import com.cloud.storage.GuestOS; +import com.cloud.storage.S3; +import com.cloud.storage.Snapshot; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Swift; +import com.cloud.storage.Volume; import com.cloud.storage.snapshot.SnapshotPolicy; import com.cloud.storage.snapshot.SnapshotSchedule; import com.cloud.template.VirtualMachineTemplate; @@ -154,18 +176,8 @@ import com.cloud.uservm.UserVm; import com.cloud.utils.net.Ip; import com.cloud.vm.InstanceGroup; import com.cloud.vm.Nic; -import com.cloud.vm.NicSecondaryIp; -import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.VirtualMachine; -import org.apache.cloudstack.api.ApiConstants.HostDetails; -import org.apache.cloudstack.api.ApiConstants.VMDetails; -import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; -import org.apache.cloudstack.api.response.*; -import org.apache.cloudstack.region.Region; - -import java.text.DecimalFormat; -import java.util.EnumSet; -import java.util.List; +import com.cloud.vm.snapshot.VMSnapshot; public interface ResponseGenerator { UserResponse createUserResponse(UserAccount user); @@ -388,14 +400,19 @@ public interface ResponseGenerator { GuestOSResponse createGuestOSResponse(GuestOS os); SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule sched); - + UsageRecordResponse createUsageResponse(Usage usageRecord); TrafficMonitorResponse createTrafficMonitorResponse(Host trafficMonitor); VMSnapshotResponse createVMSnapshotResponse(VMSnapshot vmSnapshot); + NicSecondaryIpResponse createSecondaryIPToNicResponse(String ip, Long nicId, Long networkId); public NicResponse createNicResponse(Nic result); ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb, Map lbInstances); + + AffinityGroupResponse createAffinityGroupResponse(AffinityGroup group); + + Long getAffinityGroupId(String name, long entityOwnerId); } diff --git a/api/src/org/apache/cloudstack/api/command/admin/config/ListDeploymentPlannersCmd.java b/api/src/org/apache/cloudstack/api/command/admin/config/ListDeploymentPlannersCmd.java new file mode 100644 index 00000000000..69004de2a6b --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/config/ListDeploymentPlannersCmd.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.config; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.response.DeploymentPlannersResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.log4j.Logger; + +@APICommand(name = "listDeploymentPlanners", description = "Lists all DeploymentPlanners available.", responseObject = DeploymentPlannersResponse.class) +public class ListDeploymentPlannersCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(ListDeploymentPlannersCmd.class.getName()); + + private static final String s_name = "listdeploymentplannersresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute(){ + List planners = _mgr.listDeploymentPlanners(); + ListResponse response = new ListResponse(); + List plannerResponses = new ArrayList(); + + for (String planner : planners) { + DeploymentPlannersResponse plannerResponse = new DeploymentPlannersResponse(); + plannerResponse.setName(planner); + plannerResponse.setObjectName("deploymentPlanner"); + plannerResponses.add(plannerResponse); + } + + response.setResponses(plannerResponses); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + + } +} diff --git a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index e915c48e9b6..74392cd4299 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -84,6 +84,9 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name=ApiConstants.NETWORKRATE, type=CommandType.INTEGER, description="data transfer rate in megabits per second allowed. Supported only for non-System offering and system offerings having \"domainrouter\" systemvmtype") private Integer networkRate; + @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "The deployment planner heuristics used to deploy a VM of this offering, default \"FirstFitPlanner\".") + private String deploymentPlanner; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -148,6 +151,10 @@ public class CreateServiceOfferingCmd extends BaseCmd { return networkRate; } + public String getDeploymentPlanner() { + return deploymentPlanner; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/org/apache/cloudstack/api/command/user/affinitygroup/CreateAffinityGroupCmd.java b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/CreateAffinityGroupCmd.java new file mode 100644 index 00000000000..96de4aa13c0 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/CreateAffinityGroupCmd.java @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.affinitygroup; + +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.log4j.Logger; + +import com.cloud.async.AsyncJob; +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.user.Account; +import com.cloud.user.UserContext; + +@APICommand(name = "createAffinityGroup", responseObject = AffinityGroupResponse.class, description = "Creates an affinity/anti-affinity group") +public class CreateAffinityGroupCmd extends BaseAsyncCreateCmd { + public static final Logger s_logger = Logger.getLogger(CreateAffinityGroupCmd.class.getName()); + + private static final String s_name = "createaffinitygroupresponse"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an account for the affinity group. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, description = "domainId of the account owning the affinity group", entityType = DomainResponse.class) + private Long domainId; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "optional description of the affinity group") + private String description; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "name of the affinity group") + private String affinityGroupName; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "Type of the affinity group from the available affinity/anti-affinity group types") + private String affinityGroupType; + + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + public String getAccountName() { + return accountName; + } + + public String getDescription() { + return description; + } + + public Long getDomainId() { + return domainId; + } + + public String getAffinityGroupName() { + return affinityGroupName; + } + + public String getAffinityGroupType() { + return affinityGroupType; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = UserContext.current().getCaller(); + if ((account == null) || isAdmin(account.getType())) { + if ((domainId != null) && (accountName != null)) { + Account userAccount = _responseGenerator.findAccountByNameDomain(accountName, domainId); + if (userAccount != null) { + return userAccount.getId(); + } + } + } + + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this + // command to SYSTEM so ERROR events + // are tracked + } + + @Override + public void execute() { + AffinityGroup group = _affinityGroupService.getAffinityGroup(getEntityId()); + if (group != null) { + AffinityGroupResponse response = _responseGenerator.createAffinityGroupResponse(group); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create affinity group:" + + affinityGroupName); + } + } + + @Override + public void create() throws ResourceAllocationException { + AffinityGroup result = _affinityGroupService.createAffinityGroup(accountName, domainId, affinityGroupName, + affinityGroupType, description); + if (result != null) { + setEntityId(result.getId()); + setEntityUuid(result.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create affinity group entity" + affinityGroupName); + } + + } + + @Override + public String getEventType() { + return EventTypes.EVENT_AFFINITY_GROUP_CREATE; + } + + @Override + public String getEventDescription() { + return "creating Affinity Group"; + } + + @Override + public String getCreateEventType() { + return EventTypes.EVENT_AFFINITY_GROUP_CREATE; + } + + @Override + public String getCreateEventDescription() { + return "creating Affinity Group"; + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.AffinityGroup; + } + +} diff --git a/api/src/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java new file mode 100644 index 00000000000..ea4a010ab93 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.affinitygroup; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.log4j.Logger; + +import com.cloud.async.AsyncJob; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceInUseException; +import com.cloud.user.Account; +import com.cloud.user.UserContext; + +@APICommand(name = "deleteAffinityGroup", description = "Deletes affinity group", responseObject = SuccessResponse.class) +public class DeleteAffinityGroupCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(DeleteAffinityGroupCmd.class.getName()); + private static final String s_name = "deleteaffinitygroupresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the account of the affinity group. Must be specified with domain ID") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, description = "the domain ID of account owning the affinity group", entityType = DomainResponse.class) + private Long domainId; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, description = "The ID of the affinity group. Mutually exclusive with name parameter", entityType = AffinityGroupResponse.class) + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "The name of the affinity group. Mutually exclusive with id parameter") + private String name; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + + public Long getId() { + if (id != null && name != null) { + throw new InvalidParameterValueException("name and id parameters are mutually exclusive"); + } + + if (name != null) { + id = _responseGenerator.getAffinityGroupId(name, getEntityOwnerId()); + if (id == null) { + throw new InvalidParameterValueException("Unable to find affinity group by name " + name + + " for the account id=" + getEntityOwnerId()); + } + } + + if (id == null) { + throw new InvalidParameterValueException( + "Either id or name parameter is requred by deleteAffinityGroup command"); + } + + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = UserContext.current().getCaller(); + if ((account == null) || isAdmin(account.getType())) { + if ((domainId != null) && (accountName != null)) { + Account userAccount = _responseGenerator.findAccountByNameDomain(accountName, domainId); + if (userAccount != null) { + return userAccount.getId(); + } + } + } + + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this + // command to SYSTEM so ERROR events + // are tracked + + } + + @Override + public void execute(){ + try{ + boolean result = _affinityGroupService.deleteAffinityGroup(id, accountName, domainId, name); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete affinity group"); + } + } catch (ResourceInUseException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_IN_USE_ERROR, ex.getMessage()); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_AFFINITY_GROUP_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting Affinity Group"; + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.AffinityGroup; + } +} diff --git a/api/src/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupTypesCmd.java b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupTypesCmd.java new file mode 100644 index 00000000000..ce6e89a078c --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupTypesCmd.java @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.affinitygroup; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.affinity.AffinityGroupTypeResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.log4j.Logger; + +import com.cloud.user.Account; + +@APICommand(name = "listAffinityGroupTypes", description = "Lists affinity group types available", responseObject = AffinityGroupTypeResponse.class) +public class ListAffinityGroupTypesCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(ListAffinityGroupTypesCmd.class.getName()); + + private static final String s_name = "listaffinitygrouptypesresponse"; + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + List result = _affinityGroupService.listAffinityGroupTypes(); + ListResponse response = new ListResponse(); + ArrayList responses = new ArrayList(); + if (result != null) { + for (String type : result) { + AffinityGroupTypeResponse affinityTypeResponse = new AffinityGroupTypeResponse(); + affinityTypeResponse.setType(type); + affinityTypeResponse.setObjectName("affinityGroupType"); + responses.add(affinityTypeResponse); + } + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } +} diff --git a/api/src/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupsCmd.java b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupsCmd.java new file mode 100644 index 00000000000..9310fb91016 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupsCmd.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.affinitygroup; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.log4j.Logger; + +import com.cloud.async.AsyncJob; + +@APICommand(name = "listAffinityGroups", description = "Lists affinity groups", responseObject = AffinityGroupResponse.class) +public class ListAffinityGroupsCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(ListAffinityGroupsCmd.class.getName()); + + private static final String s_name = "listaffinitygroupsresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "lists affinity groups by name") + private String affinityGroupName; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, description = "lists affinity groups by virtual machine id", entityType = UserVmResponse.class) + private Long virtualMachineId; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, description = "list the affinity group by the id provided", entityType = AffinityGroupResponse.class) + private Long id; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "lists affinity groups by type") + private String affinityGroupType; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + public String getAffinityGroupName() { + return affinityGroupName; + } + + public Long getVirtualMachineId() { + return virtualMachineId; + } + + public Long getId(){ + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute(){ + + ListResponse response = _queryService.listAffinityGroups(id, affinityGroupName, + affinityGroupType, virtualMachineId, this.getStartIndex(), this.getPageSizeVal()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.AffinityGroup; + } +} diff --git a/api/src/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java new file mode 100644 index 00000000000..44d017b7d8b --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java @@ -0,0 +1,164 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.affinitygroup; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.ApiConstants.VMDetails; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.log4j.Logger; + +import com.cloud.async.AsyncJob; +import com.cloud.event.EventTypes; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; + + +@APICommand(name = "updateVMAffinityGroup", description = "Updates the affinity/anti-affinity group associations of a virtual machine. The VM has to be stopped and restarted for the " + + "new properties to take effect.", responseObject = UserVmResponse.class) +public class UpdateVMAffinityGroupCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(UpdateVMAffinityGroupCmd.class.getName()); + private static final String s_name = "updatevirtualmachineresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + + @ACL + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=UserVmResponse.class, + required=true, description="The ID of the virtual machine") + private Long id; + + @ACL + @Parameter(name = ApiConstants.AFFINITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups id that are going to be applied to the virtual machine. " + + "Should be passed only when vm is created from a zone with Basic Network support." + + " Mutually exclusive with securitygroupnames parameter") + private List affinityGroupIdList; + + @ACL + @Parameter(name = ApiConstants.AFFINITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups names that are going to be applied to the virtual machine." + + " Should be passed only when vm is created from a zone with Basic Network support. " + + "Mutually exclusive with securitygroupids parameter") + private List affinityGroupNameList; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public Long getId() { + return id; + } + + + public List getAffinityGroupIdList() { + if (affinityGroupNameList != null && affinityGroupIdList != null) { + throw new InvalidParameterValueException( + "affinitygroupids parameter is mutually exclusive with affinitygroupnames parameter"); + } + + // transform group names to ids here + if (affinityGroupNameList != null) { + List affinityGroupIds = new ArrayList(); + for (String groupName : affinityGroupNameList) { + Long groupId = _responseGenerator.getAffinityGroupId(groupName, getEntityOwnerId()); + if (groupId == null) { + throw new InvalidParameterValueException("Unable to find group by name " + groupName + + " for account " + getEntityOwnerId()); + } else { + affinityGroupIds.add(groupId); + } + } + return affinityGroupIds; + } else { + return affinityGroupIdList; + } + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "virtualmachine"; + } + + @Override + public long getEntityOwnerId() { + UserVm userVm = _entityMgr.findById(UserVm.class, getId()); + if (userVm != null) { + return userVm.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public void execute() throws ResourceUnavailableException, + InsufficientCapacityException, ServerApiException { + UserContext.current().setEventDetails("Vm Id: "+getId()); + UserVm result = _affinityGroupService.updateVMAffinityGroups(getId(), getAffinityGroupIdList()); + ArrayList dc = new ArrayList(); + dc.add(VMDetails.valueOf("affgrp")); + EnumSet details = EnumSet.copyOf(dc); + + if (result != null){ + UserVmResponse response = _responseGenerator.createUserVmResponse("virtualmachine", details, result).get(0); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update vm's affinity groups"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_AFFINITY_GROUP_UPDATE; + } + + @Override + public String getEventDescription() { + return "updating VM Affinity Group"; + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.AffinityGroup; + } + +} diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 21a45f8cc7f..77ba9fed59e 100755 --- a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -172,6 +173,16 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { @Parameter(name=ApiConstants.START_VM, type=CommandType.BOOLEAN, description="true if network offering supports specifying ip ranges; defaulted to true if not specified") private Boolean startVm; + @ACL + @Parameter(name = ApiConstants.AFFINITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups id that are going to be applied to the virtual machine." + + " Mutually exclusive with affinitygroupnames parameter") + private List affinityGroupIdList; + + @ACL + @Parameter(name = ApiConstants.AFFINITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups names that are going to be applied to the virtual machine." + + "Mutually exclusive with affinitygroupids parameter") + private List affinityGroupNameList; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -321,6 +332,30 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { return ip6Address.toLowerCase(); } + public List getAffinityGroupIdList() { + if (affinityGroupNameList != null && affinityGroupIdList != null) { + throw new InvalidParameterValueException( + "affinitygroupids parameter is mutually exclusive with affinitygroupnames parameter"); + } + + // transform group names to ids here + if (affinityGroupNameList != null) { + List affinityGroupIds = new ArrayList(); + for (String groupName : affinityGroupNameList) { + Long groupId = _responseGenerator.getAffinityGroupId(groupName, getEntityOwnerId()); + if (groupId == null) { + throw new InvalidParameterValueException("Unable to find group by name " + groupName + + " for account " + getEntityOwnerId()); + } else { + affinityGroupIds.add(groupId); + } + } + return affinityGroupIds; + } else { + return affinityGroupIdList; + } + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -447,18 +482,18 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { throw new InvalidParameterValueException("Can't specify network Ids in Basic zone"); } else { vm = _userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(), owner, name, - displayName, diskOfferingId, size, group, getHypervisor(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, keyboard); + displayName, diskOfferingId, size, group, getHypervisor(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, keyboard, getAffinityGroupIdList()); } } else { if (zone.isSecurityGroupEnabled()) { vm = _userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, getNetworkIds(), getSecurityGroupIdList(), - owner, name, displayName, diskOfferingId, size, group, getHypervisor(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, keyboard); + owner, name, displayName, diskOfferingId, size, group, getHypervisor(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, keyboard, getAffinityGroupIdList()); } else { if (getSecurityGroupIdList() != null && !getSecurityGroupIdList().isEmpty()) { throw new InvalidParameterValueException("Can't create vm with security groups; security group feature is not enabled per zone"); } vm = _userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, getNetworkIds(), owner, name, displayName, - diskOfferingId, size, group, getHypervisor(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, keyboard); + diskOfferingId, size, group, getHypervisor(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, keyboard, getAffinityGroupIdList()); } } diff --git a/api/src/org/apache/cloudstack/api/response/DeploymentPlannersResponse.java b/api/src/org/apache/cloudstack/api/response/DeploymentPlannersResponse.java new file mode 100644 index 00000000000..a37800fd650 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/DeploymentPlannersResponse.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class DeploymentPlannersResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "Deployment Planner name") + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/api/src/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/org/apache/cloudstack/api/response/UserVmResponse.java index 71d1b823c6a..212601ce20c 100644 --- a/api/src/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/org/apache/cloudstack/api/response/UserVmResponse.java @@ -21,6 +21,7 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; @@ -169,10 +170,15 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp @SerializedName(ApiConstants.SSH_KEYPAIR) @Param(description="ssh key-pair") private String keyPairName; + @SerializedName("affinitygroup") + @Param(description = "list of affinity groups associated with the virtual machine", responseObject = AffinityGroupResponse.class) + private Set affinityGroupList; + public UserVmResponse(){ securityGroupList = new LinkedHashSet(); nics = new LinkedHashSet(); tags = new LinkedHashSet(); + affinityGroupList = new LinkedHashSet(); } public void setHypervisor(String hypervisor) { @@ -381,4 +387,12 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp this.keyPairName = keyPairName; } + public void setAffinityGroupList(Set affinityGroups) { + this.affinityGroupList = affinityGroups; + } + + public void addAffinityGroup(AffinityGroupResponse affinityGroup) { + this.affinityGroupList.add(affinityGroup); + } + } diff --git a/api/src/org/apache/cloudstack/query/QueryService.java b/api/src/org/apache/cloudstack/query/QueryService.java index c3f86aabb7f..443c5df65b5 100644 --- a/api/src/org/apache/cloudstack/query/QueryService.java +++ b/api/src/org/apache/cloudstack/query/QueryService.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.query; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; @@ -97,4 +98,7 @@ public interface QueryService { public ListResponse searchForServiceOfferings(ListServiceOfferingsCmd cmd); public ListResponse listDataCenters(ListZonesByCmd cmd); + + public ListResponse listAffinityGroups(Long affinityGroupId, String affinityGroupName, + String affinityGroupType, Long vmId, Long startIndex, Long pageSize); } diff --git a/client/pom.xml b/client/pom.xml index fbc0acd4bba..a62ff498424 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -239,6 +239,11 @@ cloud-plugin-snmp-alerts ${project.version} + + org.apache.cloudstack + cloud-plugin-host-anti-affinity + ${project.version} + install @@ -289,7 +294,7 @@ --> copy-systemvm - package + process-resources run diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 9fa32341607..7963071b809 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -842,5 +842,15 @@ --> + + + + + + + + + + diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 2145a5272d3..35f7ed200f1 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -580,3 +580,9 @@ createInternalLoadBalancerElement=1 listInternalLoadBalancerElements=1 +#### Affinity group commands +createAffinityGroup=15 +deleteAffinityGroup=15 +listAffinityGroups=15 +updateVMAffinityGroup=15 +listAffinityGroupTypes=15 diff --git a/client/tomcatconf/componentContext.xml.in b/client/tomcatconf/componentContext.xml.in index f96a33078c6..2085b536a39 100644 --- a/client/tomcatconf/componentContext.xml.in +++ b/client/tomcatconf/componentContext.xml.in @@ -31,6 +31,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + @@ -250,5 +250,12 @@ - + + + + + + diff --git a/client/tomcatconf/simulatorComponentContext.xml.in b/client/tomcatconf/simulatorComponentContext.xml.in index fc5cf540bd0..435b9e2fc62 100644 --- a/client/tomcatconf/simulatorComponentContext.xml.in +++ b/client/tomcatconf/simulatorComponentContext.xml.in @@ -1,4 +1,3 @@ - - - + + @@ -122,9 +121,9 @@ - - - + + + + + + + + diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java index 0359db95a96..ec813b2e998 100755 --- a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java @@ -36,7 +36,9 @@ import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -55,7 +57,7 @@ import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; -import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; @@ -69,42 +71,45 @@ public class VMEntityManagerImpl implements VMEntityManager { protected VMInstanceDao _vmDao; @Inject protected VMTemplateDao _templateDao = null; - + @Inject protected ServiceOfferingDao _serviceOfferingDao; - + @Inject protected DiskOfferingDao _diskOfferingDao = null; - + @Inject protected NetworkDao _networkDao; - + @Inject protected AccountDao _accountDao = null; @Inject protected UserDao _userDao = null; - @Inject + @Inject protected VMEntityDao _vmEntityDao; - - @Inject + + @Inject protected VMReservationDao _reservationDao; - + @Inject protected VirtualMachineManager _itMgr; - + @Inject protected List _planners; - + @Inject protected VolumeDao _volsDao; - + @Inject protected PrimaryDataStoreDao _storagePoolDao; @Inject DataStoreManager dataStoreMgr; - + + @Inject + DeploymentPlanningManager _dpMgr; + @Override public VMEntityVO loadVirtualMachine(String vmId) { // TODO Auto-generated method stub @@ -114,11 +119,11 @@ public class VMEntityManagerImpl implements VMEntityManager { @Override public void saveVirtualMachine(VMEntityVO entity) { _vmEntityDao.persist(entity); - + } @Override - public String reserveVirtualMachine(VMEntityVO vmEntityVO, String plannerToUse, DeploymentPlan planToDeploy, ExcludeList exclude) + public String reserveVirtualMachine(VMEntityVO vmEntityVO, String plannerToUse, DeploymentPlan planToDeploy, ExcludeList exclude) throws InsufficientCapacityException, ResourceUnavailableException { //call planner and get the deployDestination. @@ -130,12 +135,12 @@ public class VMEntityManagerImpl implements VMEntityManager { if(planToDeploy != null && planToDeploy.getDataCenterId() != 0){ plan = new DataCenterDeployment(planToDeploy.getDataCenterId(), planToDeploy.getPodId(), planToDeploy.getClusterId(), planToDeploy.getHostId(), planToDeploy.getPoolId(), planToDeploy.getPhysicalNetworkId()); } - + List vols = _volsDao.findReadyRootVolumesByInstance(vm.getId()); if(!vols.isEmpty()){ VolumeVO vol = vols.get(0); StoragePool pool = (StoragePool)this.dataStoreMgr.getPrimaryDataStore(vol.getPoolId()); - + if (!pool.isInMaintenance()) { long rootVolDcId = pool.getDataCenterId(); Long rootVolPodId = pool.getPodId(); @@ -156,21 +161,21 @@ public class VMEntityManagerImpl implements VMEntityManager { } } - + + } + + DeployDestination dest; + try { + dest = _dpMgr.planDeployment(vmProfile, plan, exclude); + } catch (AffinityConflictException e) { + throw new CloudRuntimeException("Unable to create deployment, affinity rules associted to the VM conflict"); } - - DeploymentPlanner planner = ComponentContext.getComponent(plannerToUse); - DeployDestination dest = null; - - if (planner.canHandle(vmProfile, plan, exclude)) { - dest = planner.plan(vmProfile, plan, exclude); - } if (dest != null) { //save destination with VMEntityVO VMReservationVO vmReservation = new VMReservationVO(vm.getId(), dest.getDataCenter().getId(), dest.getPod().getId(), dest.getCluster().getId(), dest.getHost().getId()); Map volumeReservationMap = new HashMap(); - + if (vm.getHypervisorType() != HypervisorType.BareMetal) { for(Volume vo : dest.getStorageForDisks().keySet()){ volumeReservationMap.put(vo.getId(), dest.getStorageForDisks().get(vo).getId()); @@ -180,21 +185,21 @@ public class VMEntityManagerImpl implements VMEntityManager { vmEntityVO.setVmReservation(vmReservation); _vmEntityDao.persist(vmEntityVO); - + return vmReservation.getUuid(); }else{ throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId()); } - + } @Override public void deployVirtualMachine(String reservationId, String caller, Map params) throws InsufficientCapacityException, ResourceUnavailableException{ //grab the VM Id and destination using the reservationId. - + VMReservationVO vmReservation = _reservationDao.findByReservationId(reservationId); long vmId = vmReservation.getVmId(); - + VMInstanceVO vm = _vmDao.findById(vmId); //Pass it down Long poolId = null; @@ -205,12 +210,17 @@ public class VMEntityManagerImpl implements VMEntityManager { poolId = storage.get(volIdList.get(0)); } } - - DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), vmReservation.getPodId(), vmReservation.getClusterId(), + + DataCenterDeployment reservedPlan = new DataCenterDeployment(vm.getDataCenterId(), vmReservation.getPodId(), vmReservation.getClusterId(), vmReservation.getHostId(), null , null); - - VMInstanceVO vmDeployed = _itMgr.start(vm, params, _userDao.findById(new Long(caller)), _accountDao.findById(vm.getAccountId()), plan); - + try{ + VMInstanceVO vmDeployed = _itMgr.start(vm, params, _userDao.findById(new Long(caller)), _accountDao.findById(vm.getAccountId()), reservedPlan); + }catch(Exception ex){ + //Retry the deployment without using the reservation plan + DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), null, null,null, null , null); + _itMgr.start(vm, params, _userDao.findById(new Long(caller)), _accountDao.findById(vm.getAccountId()), plan); + } + } @Override @@ -227,7 +237,7 @@ public class VMEntityManagerImpl implements VMEntityManager { VMInstanceVO vm = _vmDao.findByUuid(vmEntityVO.getUuid()); return _itMgr.destroy(vm, _userDao.findById(new Long(caller)), _accountDao.findById(vm.getAccountId())); - + } } diff --git a/engine/orchestration/src/org/apache/cloudstack/platform/orchestration/CloudOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/platform/orchestration/CloudOrchestrator.java index c07931befc8..963e4d7d967 100755 --- a/engine/orchestration/src/org/apache/cloudstack/platform/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/platform/orchestration/CloudOrchestrator.java @@ -51,7 +51,6 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; import com.cloud.vm.NicProfile; -import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.UserVmDao; @@ -66,25 +65,25 @@ public class CloudOrchestrator implements OrchestrationService { @Inject private VirtualMachineManager _itMgr; - + @Inject protected VMTemplateDao _templateDao = null; - + @Inject protected VMInstanceDao _vmDao; - + @Inject protected UserVmDao _userVmDao = null; - + @Inject protected ServiceOfferingDao _serviceOfferingDao; - + @Inject protected DiskOfferingDao _diskOfferingDao = null; - + @Inject protected NetworkDao _networkDao; - + @Inject protected AccountDao _accountDao = null; @@ -147,17 +146,17 @@ public class CloudOrchestrator implements OrchestrationService { @Override public VirtualMachineEntity createVirtualMachine( - String id, - String owner, - String templateId, - String hostName, + String id, + String owner, + String templateId, + String hostName, String displayName, String hypervisor, - int cpu, - int speed, + int cpu, + int speed, long memory, Long diskSize, - List computeTags, + List computeTags, List rootDiskTags, Map networkNicMap, DeploymentPlan plan) throws InsufficientCapacityException { @@ -173,22 +172,22 @@ public class CloudOrchestrator implements OrchestrationService { VirtualMachineEntityImpl vmEntity = ComponentContext.inject(VirtualMachineEntityImpl.class); vmEntity.init(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, new ArrayList(networkNicMap.keySet())); - - + + HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); //load vm instance and offerings and call virtualMachineManagerImpl VMInstanceVO vm = _vmDao.findByUuid(id); - + // If the template represents an ISO, a disk offering must be passed in, and will be used to create the root disk // Else, a disk offering is optional, and if present will be used to create the data disk Pair rootDiskOffering = new Pair(null, null); List> dataDiskOfferings = new ArrayList>(); - + ServiceOfferingVO offering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); rootDiskOffering.first(offering); - + if(vm.getDiskOfferingId() != null){ DiskOfferingVO diskOffering = _diskOfferingDao.findById(vm.getDiskOfferingId()); if (diskOffering == null) { @@ -205,32 +204,32 @@ public class CloudOrchestrator implements OrchestrationService { } dataDiskOfferings.add(new Pair(diskOffering, size)); } - - - + + + if (_itMgr.allocate(_userVmDao.findById(vm.getId(), true), _templateDao.findById(new Long(templateId)), offering, rootDiskOffering, dataDiskOfferings, networkIpMap, null, plan, hypervisorType, _accountDao.findById(new Long(owner))) == null) { return null; } - + return vmEntity; } @Override public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os, int cpu, int speed, long memory,Long diskSize, List computeTags, List rootDiskTags, Map networkNicMap, DeploymentPlan plan) throws InsufficientCapacityException { - + // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager); VirtualMachineEntityImpl vmEntity = ComponentContext.inject(VirtualMachineEntityImpl.class); vmEntity.init(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, new ArrayList(networkNicMap.keySet())); //load vm instance and offerings and call virtualMachineManagerImpl VMInstanceVO vm = _vmDao.findByUuid(id); - - + + Pair rootDiskOffering = new Pair(null, null); ServiceOfferingVO offering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); rootDiskOffering.first(offering); - + List> dataDiskOfferings = new ArrayList>(); Long diskOfferingId = vm.getDiskOfferingId(); if (diskOfferingId == null) { @@ -251,7 +250,7 @@ public class CloudOrchestrator implements OrchestrationService { } rootDiskOffering.first(diskOffering); rootDiskOffering.second(size); - + List> networkIpMap = new ArrayList>(); for (String uuid : networkNicMap.keySet()) { NetworkVO network = _networkDao.findByUuid(uuid); @@ -259,13 +258,13 @@ public class CloudOrchestrator implements OrchestrationService { networkIpMap.add(new Pair(network, networkNicMap.get(uuid))); } } - + HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); - + if (_itMgr.allocate(_userVmDao.findById(vm.getId(), true), _templateDao.findById(new Long(isoId)), offering, rootDiskOffering, dataDiskOfferings, networkIpMap, null, plan, hypervisorType, _accountDao.findById(new Long(owner))) == null) { return null; } - + return vmEntity; } diff --git a/plugins/affinity-group-processors/host-anti-affinity/pom.xml b/plugins/affinity-group-processors/host-anti-affinity/pom.xml new file mode 100644 index 00000000000..669febd7db8 --- /dev/null +++ b/plugins/affinity-group-processors/host-anti-affinity/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + cloud-plugin-host-anti-affinity + Apache CloudStack Plugin - Host Anti-Affinity Processor + + org.apache.cloudstack + cloudstack-plugins + 4.2.0-SNAPSHOT + ../../pom.xml + + + install + src + + diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java new file mode 100644 index 00000000000..430cf92659d --- /dev/null +++ b/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.util.List; + +import javax.ejb.Local; +import javax.inject.Inject; + +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.log4j.Logger; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Local(value = AffinityGroupProcessor.class) +public class HostAntiAffinityProcessor extends AffinityProcessorBase implements AffinityGroupProcessor { + + private static final Logger s_logger = Logger.getLogger(HostAntiAffinityProcessor.class); + @Inject + protected UserVmDao _vmDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected AffinityGroupDao _affinityGroupDao; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + + @Override + public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, + ExcludeList avoid) + throws AffinityConflictException { + VirtualMachine vm = vmProfile.getVirtualMachine(); + AffinityGroupVMMapVO vmGroupMapping = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + + if (vmGroupMapping != null) { + AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); + } + + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); + if (groupVM != null && !groupVM.isRemoved()) { + if (groupVM.getHostId() != null) { + avoid.addHost(groupVM.getHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + + groupVM.getId() + " is present on the host"); + } + } else if (VirtualMachine.State.Stopped.equals(groupVM.getState()) + && groupVM.getLastHostId() != null) { + avoid.addHost(groupVM.getLastHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + + groupVM.getId() + " is present on the host, in Stopped state"); + } + + } + } + } + } + + } + +} diff --git a/plugins/pom.xml b/plugins/pom.xml index 763b20b5f59..f5dc9695ea6 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -36,6 +36,7 @@ api/rate-limit api/discovery acl/static-role-based + affinity-group-processors/host-anti-affinity deployment-planners/user-concentrated-pod deployment-planners/user-dispersing host-allocators/random diff --git a/server/src/com/cloud/api/ApiDBUtils.java b/server/src/com/cloud/api/ApiDBUtils.java index 464592a25e7..e3330eca72d 100755 --- a/server/src/com/cloud/api/ApiDBUtils.java +++ b/server/src/com/cloud/api/ApiDBUtils.java @@ -25,6 +25,9 @@ import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.response.AccountResponse; @@ -51,6 +54,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.springframework.stereotype.Component; import com.cloud.api.query.dao.AccountJoinDao; +import com.cloud.api.query.dao.AffinityGroupJoinDao; import com.cloud.api.query.dao.AsyncJobJoinDao; import com.cloud.api.query.dao.DataCenterJoinDao; import com.cloud.api.query.dao.DiskOfferingJoinDao; @@ -68,6 +72,7 @@ import com.cloud.api.query.dao.UserAccountJoinDao; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.dao.VolumeJoinDao; import com.cloud.api.query.vo.AccountJoinVO; +import com.cloud.api.query.vo.AffinityGroupJoinVO; import com.cloud.api.query.vo.AsyncJobJoinVO; import com.cloud.api.query.vo.DataCenterJoinVO; import com.cloud.api.query.vo.DiskOfferingJoinVO; @@ -285,7 +290,7 @@ public class ApiDBUtils { static NetworkModel _networkModel; static NetworkManager _networkMgr; static TemplateManager _templateMgr; - + static StatsCollector _statsCollector; static AccountDao _accountDao; @@ -380,6 +385,8 @@ public class ApiDBUtils { static ClusterDetailsDao _clusterDetailsDao; static NicSecondaryIpDao _nicSecondaryIpDao; static VpcProvisioningService _vpcProvSvc; + static AffinityGroupDao _affinityGroupDao; + static AffinityGroupJoinDao _affinityGroupJoinDao; @Inject private ManagementServer ms; @Inject public AsyncJobManager asyncMgr; @@ -485,6 +492,9 @@ public class ApiDBUtils { @Inject private NicSecondaryIpDao nicSecondaryIpDao; @Inject private VpcProvisioningService vpcProvSvc; @Inject private ApplicationLoadBalancerRuleDao _appLbDao; + @Inject private AffinityGroupDao affinityGroupDao; + @Inject private AffinityGroupJoinDao affinityGroupJoinDao; + @PostConstruct void init() { _ms = ms; @@ -587,6 +597,8 @@ public class ApiDBUtils { _vmSnapshotDao = vmSnapshotDao; _nicSecondaryIpDao = nicSecondaryIpDao; _vpcProvSvc = vpcProvSvc; + _affinityGroupDao = affinityGroupDao; + _affinityGroupJoinDao = affinityGroupJoinDao; // Note: stats collector should already have been initialized by this time, otherwise a null instance is returned _statsCollector = StatsCollector.getInstance(); } @@ -1581,7 +1593,7 @@ public class ApiDBUtils { public static DataCenterJoinVO newDataCenterView(DataCenter dc){ return _dcJoinDao.newDataCenterView(dc); } - + public static Map findHostDetailsById(long hostId){ return _hostDetailsDao.findDetails(hostId); } @@ -1589,4 +1601,16 @@ public class ApiDBUtils { public static List findNicSecondaryIps(long nicId) { return _nicSecondaryIpDao.listByNicId(nicId); } + + public static AffinityGroup getAffinityGroup(String groupName, long accountId) { + return _affinityGroupDao.findByAccountAndName(accountId, groupName); + } + + public static AffinityGroupResponse newAffinityGroupResponse(AffinityGroupJoinVO group) { + return _affinityGroupJoinDao.newAffinityGroupResponse(group); + } + + public static AffinityGroupResponse fillAffinityGroupDetails(AffinityGroupResponse resp, AffinityGroupJoinVO group) { + return _affinityGroupJoinDao.setAffinityGroupResponse(resp, group); + } } diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 993e3749d71..476038d51a1 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -36,6 +36,8 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.BaseCmd; @@ -445,7 +447,7 @@ public class ApiResponseHelper implements ResponseGenerator { vmSnapshotResponse.setCreated(vmSnapshot.getCreated()); vmSnapshotResponse.setDescription(vmSnapshot.getDescription()); vmSnapshotResponse.setDisplayName(vmSnapshot.getDisplayName()); - UserVm vm = ApiDBUtils.findUserVmById(vmSnapshot.getVmId()); + UserVm vm = ApiDBUtils.findUserVmById(vmSnapshot.getVmId()); if(vm!=null) vmSnapshotResponse.setVirtualMachineid(vm.getUuid()); if(vmSnapshot.getParent() != null) @@ -455,7 +457,7 @@ public class ApiResponseHelper implements ResponseGenerator { vmSnapshotResponse.setObjectName("vmsnapshot"); return vmSnapshotResponse; } - + @Override public SnapshotPolicyResponse createSnapshotPolicyResponse(SnapshotPolicy policy) { SnapshotPolicyResponse policyResponse = new SnapshotPolicyResponse(); @@ -552,7 +554,7 @@ public class ApiResponseHelper implements ResponseGenerator { vlanResponse.setIp6Gateway(vlan.getIp6Gateway()); vlanResponse.setIp6Cidr(vlan.getIp6Cidr()); - + String ip6Range = vlan.getIp6Range(); if (ip6Range != null) { String[] range = ip6Range.split("-"); @@ -2256,7 +2258,7 @@ public class ApiResponseHelper implements ResponseGenerator { if (((network.getCidr()) != null) && (network.getNetworkCidr() == null)) { response.setNetmask(NetUtils.cidr2Netmask(network.getCidr())); } - + response.setIp6Gateway(network.getIp6Gateway()); response.setIp6Cidr(network.getIp6Cidr()); @@ -2451,7 +2453,7 @@ public class ApiResponseHelper implements ResponseGenerator { List cidrs = ApiDBUtils.findFirewallSourceCidrs(fwRule.getId()); response.setCidrList(StringUtils.join(cidrs, ",")); - + if (fwRule.getTrafficType() == FirewallRule.TrafficType.Ingress) { IpAddress ip = ApiDBUtils.findIpAddressById(fwRule.getSourceIpAddressId()); response.setPublicIpAddressId(ip.getId()); @@ -3483,7 +3485,7 @@ public class ApiResponseHelper implements ResponseGenerator { return usageRecResponse; } - + public String getDateStringInternal(Date inputDate) { if (inputDate == null) return null; @@ -3567,7 +3569,7 @@ public class ApiResponseHelper implements ResponseGenerator { return sb.toString(); } - + @Override public TrafficMonitorResponse createTrafficMonitorResponse(Host trafficMonitor) { Map tmDetails = ApiDBUtils.findHostDetailsById(trafficMonitor.getId()); @@ -3627,6 +3629,7 @@ public class ApiResponseHelper implements ResponseGenerator { response.setIsDefault(result.isDefaultNic()); return response; } + @Override public ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb, Map lbInstances) { @@ -3693,4 +3696,29 @@ public class ApiResponseHelper implements ResponseGenerator { lbResponse.setObjectName("loadbalancer"); return lbResponse; } + + @Override + public AffinityGroupResponse createAffinityGroupResponse(AffinityGroup group) { + + AffinityGroupResponse response = new AffinityGroupResponse(); + + Account account = ApiDBUtils.findAccountById(group.getAccountId()); + response.setAccountName(account.getAccountName()); + response.setName(group.getName()); + response.setType(group.getType()); + response.setDescription(group.getDescription()); + // response.setDomainId(account.) + + return response; + } + + @Override + public Long getAffinityGroupId(String groupName, long accountId) { + AffinityGroup ag = ApiDBUtils.getAffinityGroup(groupName, accountId); + if (ag == null) { + return null; + } else { + return ag.getId(); + } + } } diff --git a/server/src/com/cloud/api/query/QueryManagerImpl.java b/server/src/com/cloud/api/query/QueryManagerImpl.java index 951d09ed185..a498c183af5 100644 --- a/server/src/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/com/cloud/api/query/QueryManagerImpl.java @@ -27,6 +27,11 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; +import org.apache.cloudstack.affinity.AffinityGroupVO; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; @@ -69,6 +74,7 @@ import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.api.query.dao.AccountJoinDao; +import com.cloud.api.query.dao.AffinityGroupJoinDao; import com.cloud.api.query.dao.AsyncJobJoinDao; import com.cloud.api.query.dao.DataCenterJoinDao; import com.cloud.api.query.dao.DiskOfferingJoinDao; @@ -86,6 +92,7 @@ import com.cloud.api.query.dao.UserAccountJoinDao; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.dao.VolumeJoinDao; import com.cloud.api.query.vo.AccountJoinVO; +import com.cloud.api.query.vo.AffinityGroupJoinVO; import com.cloud.api.query.vo.AsyncJobJoinVO; import com.cloud.api.query.vo.DataCenterJoinVO; import com.cloud.api.query.vo.DiskOfferingJoinVO; @@ -138,6 +145,7 @@ import com.cloud.utils.Ternary; import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.Filter; +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; @@ -246,6 +254,12 @@ public class QueryManagerImpl extends ManagerBase implements QueryService { @Inject private HighAvailabilityManager _haMgr; + @Inject + AffinityGroupVMMapDao _affinityGroupVMMapDao; + + @Inject + private AffinityGroupJoinDao _affinityGroupJoinDao; + /* (non-Javadoc) * @see com.cloud.api.query.QueryService#searchForUsers(org.apache.cloudstack.api.command.admin.user.ListUsersCmd) */ @@ -2328,5 +2342,99 @@ public class QueryManagerImpl extends ManagerBase implements QueryService { return false; } + @Override + public ListResponse listAffinityGroups(Long affinityGroupId, String affinityGroupName, + String affinityGroupType, Long vmId, Long startIndex, Long pageSize) { + Pair, Integer> result = listAffinityGroupsInternal(affinityGroupId, + affinityGroupName, affinityGroupType, vmId, startIndex, pageSize); + ListResponse response = new ListResponse(); + List agResponses = ViewResponseHelper.createAffinityGroupResponses(result.first()); + response.setResponses(agResponses, result.second()); + return response; + } + + + public Pair, Integer> listAffinityGroupsInternal(Long affinityGroupId, + String affinityGroupName, String affinityGroupType, Long vmId, Long startIndex, Long pageSize) { + + Account caller = UserContext.current().getCaller(); + + Long accountId = caller.getAccountId(); + Long domainId = caller.getDomainId(); + + if (vmId != null) { + UserVmVO userVM = _userVmDao.findById(vmId); + if (userVM == null){ + throw new InvalidParameterValueException("Unable to list affinity groups for virtual machine instance " + + vmId + "; instance not found."); + } + _accountMgr.checkAccess(caller, null, true, userVM); + return listAffinityGroupsByVM(vmId.longValue(), startIndex, pageSize); + } + + Filter searchFilter = new Filter(AffinityGroupJoinVO.class, "id", true, startIndex, pageSize); + SearchBuilder groupSearch = _affinityGroupJoinDao.createSearchBuilder(); + groupSearch.select(null, Func.DISTINCT, groupSearch.entity().getId()); // select + // distinct + + SearchCriteria sc = groupSearch.create(); + + if (accountId != null) { + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + } + + if (domainId != null) { + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + } + + if (affinityGroupId != null) { + sc.addAnd("id", SearchCriteria.Op.EQ, affinityGroupId); + } + + if (affinityGroupName != null) { + sc.addAnd("name", SearchCriteria.Op.EQ, affinityGroupName); + } + + if (affinityGroupType != null) { + sc.addAnd("type", SearchCriteria.Op.EQ, affinityGroupType); + } + + + Pair, Integer> uniqueGroupsPair = _affinityGroupJoinDao.searchAndCount(sc, + searchFilter); + // search group details by ids + Integer count = uniqueGroupsPair.second(); + if (count.intValue() == 0) { + // empty result + return uniqueGroupsPair; + } + List uniqueGroups = uniqueGroupsPair.first(); + Long[] vrIds = new Long[uniqueGroups.size()]; + int i = 0; + for (AffinityGroupJoinVO v : uniqueGroups) { + vrIds[i++] = v.getId(); + } + List vrs = _affinityGroupJoinDao.searchByIds(vrIds); + return new Pair, Integer>(vrs, count); + + } + + private Pair, Integer> listAffinityGroupsByVM(long vmId, long pageInd, long pageSize) { + Filter sf = new Filter(SecurityGroupVMMapVO.class, null, true, pageInd, pageSize); + Pair, Integer> agVmMappingPair = _affinityGroupVMMapDao.listByInstanceId(vmId, sf); + Integer count = agVmMappingPair.second(); + if (count.intValue() == 0) { + // handle empty result cases + return new Pair, Integer>(new ArrayList(), count); + } + List agVmMappings = agVmMappingPair.first(); + Long[] agIds = new Long[agVmMappings.size()]; + int i = 0; + for (AffinityGroupVMMapVO agVm : agVmMappings) { + agIds[i++] = agVm.getAffinityGroupId(); + } + List ags = _affinityGroupJoinDao.searchByIds(agIds); + return new Pair, Integer>(ags, count); + } } diff --git a/server/src/com/cloud/api/query/ViewResponseHelper.java b/server/src/com/cloud/api/query/ViewResponseHelper.java index 9e612b07d1b..dc2727e8e0d 100644 --- a/server/src/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/com/cloud/api/query/ViewResponseHelper.java @@ -21,6 +21,7 @@ import java.util.EnumSet; import java.util.Hashtable; import java.util.List; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.response.AccountResponse; @@ -45,6 +46,7 @@ import org.apache.log4j.Logger; import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.AccountJoinVO; +import com.cloud.api.query.vo.AffinityGroupJoinVO; import com.cloud.api.query.vo.AsyncJobJoinVO; import com.cloud.api.query.vo.DataCenterJoinVO; import com.cloud.api.query.vo.DiskOfferingJoinVO; @@ -123,7 +125,7 @@ public class ViewResponseHelper { // first time encountering this vm userVmData = ApiDBUtils.newUserVmResponse(objectName, userVm, details, caller); } else{ - // update nics, securitygroups, tags for 1 to many mapping fields + // update nics, securitygroups, tags, affinitygroups for 1 to many mapping fields userVmData = ApiDBUtils.fillVmDetails(userVmData, userVm); } vmDataList.put(userVm.getId(), userVmData); @@ -303,4 +305,20 @@ public class ViewResponseHelper { } return respList; } + + public static List createAffinityGroupResponses(List groups) { + Hashtable vrDataList = new Hashtable(); + for (AffinityGroupJoinVO vr : groups) { + AffinityGroupResponse vrData = vrDataList.get(vr.getId()); + if (vrData == null) { + // first time encountering this AffinityGroup + vrData = ApiDBUtils.newAffinityGroupResponse(vr); + } else { + // update vms + vrData = ApiDBUtils.fillAffinityGroupDetails(vrData, vr); + } + vrDataList.put(vr.getId(), vrData); + } + return new ArrayList(vrDataList.values()); + } } diff --git a/server/src/com/cloud/api/query/dao/AffinityGroupJoinDao.java b/server/src/com/cloud/api/query/dao/AffinityGroupJoinDao.java new file mode 100644 index 00000000000..c029b3fc4f8 --- /dev/null +++ b/server/src/com/cloud/api/query/dao/AffinityGroupJoinDao.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.api.query.dao; + +import java.util.List; + +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import com.cloud.api.query.vo.AffinityGroupJoinVO; +import com.cloud.user.Account; +import com.cloud.utils.db.GenericDao; + +public interface AffinityGroupJoinDao extends GenericDao { + + AffinityGroupResponse newAffinityGroupResponse(AffinityGroupJoinVO vsg); + + AffinityGroupResponse setAffinityGroupResponse(AffinityGroupResponse vsgData, AffinityGroupJoinVO vsg); + + List newAffinityGroupView(AffinityGroup ag); + + List searchByIds(Long... ids); +} + diff --git a/server/src/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java new file mode 100644 index 00000000000..a17679313d6 --- /dev/null +++ b/server/src/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.api.query.dao; + +import java.util.ArrayList; +import java.util.List; + +import javax.ejb.Local; +import javax.inject.Inject; + + +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.log4j.Logger; +import com.cloud.api.ApiResponseHelper; +import com.cloud.api.query.vo.AffinityGroupJoinVO; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Local(value = { AffinityGroupJoinDao.class }) +public class AffinityGroupJoinDaoImpl extends GenericDaoBase implements AffinityGroupJoinDao { + public static final Logger s_logger = Logger.getLogger(AffinityGroupJoinDaoImpl.class); + + @Inject + private ConfigurationDao _configDao; + + private final SearchBuilder agSearch; + + private final SearchBuilder agIdSearch; + + protected AffinityGroupJoinDaoImpl() { + + agSearch = createSearchBuilder(); + agSearch.and("idIN", agSearch.entity().getId(), SearchCriteria.Op.IN); + agSearch.done(); + + agIdSearch = createSearchBuilder(); + agIdSearch.and("id", agIdSearch.entity().getId(), SearchCriteria.Op.EQ); + agIdSearch.done(); + + this._count = "select count(distinct id) from affinity_group_view WHERE "; + } + + @Override + public AffinityGroupResponse newAffinityGroupResponse(AffinityGroupJoinVO vag) { + AffinityGroupResponse agResponse = new AffinityGroupResponse(); + agResponse.setId(vag.getUuid()); + agResponse.setName(vag.getName()); + agResponse.setDescription(vag.getDescription()); + + ApiResponseHelper.populateOwner(agResponse, vag); + + // update vm information + long instanceId = vag.getVmId(); + if (instanceId > 0) { + List vmIdList = new ArrayList(); + vmIdList.add(vag.getVmUuid()); + agResponse.setVMIdList(vmIdList); + } + + agResponse.setObjectName("affinitygroup"); + return agResponse; + } + + @Override + public AffinityGroupResponse setAffinityGroupResponse(AffinityGroupResponse vagData, AffinityGroupJoinVO vag) { + // update vm information + long instanceId = vag.getVmId(); + if (instanceId > 0) { + vagData.addVMId(vag.getVmUuid()); + } + return vagData; + } + + @Override + public List newAffinityGroupView(AffinityGroup ag) { + + SearchCriteria sc = agIdSearch.create(); + sc.setParameters("id", ag.getId()); + return searchIncludingRemoved(sc, null, null, false); + } + + @Override + public List searchByIds(Long... agIds) { + // set detail batch query size + int DETAILS_BATCH_SIZE = 2000; + String batchCfg = _configDao.getValue("detail.batch.query.size"); + if ( batchCfg != null ){ + DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg); + } + // query details by batches + List uvList = new ArrayList(); + // query details by batches + int curr_index = 0; + if (agIds.length > DETAILS_BATCH_SIZE) { + while ((curr_index + DETAILS_BATCH_SIZE) <= agIds.length) { + Long[] ids = new Long[DETAILS_BATCH_SIZE]; + for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) { + ids[k] = agIds[j]; + } + SearchCriteria sc = agSearch.create(); + sc.setParameters("idIN", ids); + List vms = searchIncludingRemoved(sc, null, null, false); + if (vms != null) { + uvList.addAll(vms); + } + curr_index += DETAILS_BATCH_SIZE; + } + } + if (curr_index < agIds.length) { + int batch_size = (agIds.length - curr_index); + // set the ids value + Long[] ids = new Long[batch_size]; + for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) { + ids[k] = agIds[j]; + } + SearchCriteria sc = agSearch.create(); + sc.setParameters("idIN", ids); + List vms = searchIncludingRemoved(sc, null, null, false); + if (vms != null) { + uvList.addAll(vms); + } + } + return uvList; + } +} diff --git a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 8b6abf8a3e4..4ed62262966 100644 --- a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -26,6 +26,7 @@ import java.util.Set; import javax.ejb.Local; import javax.inject.Inject; +import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; @@ -216,6 +217,20 @@ public class UserVmJoinDaoImpl extends GenericDaoBase implem userVmResponse.addTag(ApiDBUtils.newResourceTagResponse(vtag, false)); } } + + if (details.contains(VMDetails.all) || details.contains(VMDetails.affgrp)) { + Long affinityGroupId = userVm.getAffinityGroupId(); + if (affinityGroupId != null && affinityGroupId.longValue() != 0) { + AffinityGroupResponse resp = new AffinityGroupResponse(); + resp.setId(userVm.getAffinityGroupUuid()); + resp.setName(userVm.getAffinityGroupName()); + resp.setDescription(userVm.getAffinityGroupDescription()); + resp.setObjectName("affinitygroup"); + resp.setAccountName(userVm.getAccountName()); + userVmResponse.addAffinityGroup(resp); + } + } + userVmResponse.setObjectName(objectName); return userVmResponse; @@ -276,6 +291,18 @@ public class UserVmJoinDaoImpl extends GenericDaoBase implem userVmData.addTag(ApiDBUtils.newResourceTagResponse(vtag, false)); } } + + Long affinityGroupId = uvo.getAffinityGroupId(); + if (affinityGroupId != null && affinityGroupId.longValue() != 0) { + AffinityGroupResponse resp = new AffinityGroupResponse(); + resp.setId(uvo.getAffinityGroupUuid()); + resp.setName(uvo.getAffinityGroupName()); + resp.setDescription(uvo.getAffinityGroupDescription()); + resp.setObjectName("affinitygroup"); + resp.setAccountName(uvo.getAccountName()); + userVmData.addAffinityGroup(resp); + } + return userVmData; } diff --git a/server/src/com/cloud/api/query/vo/AffinityGroupJoinVO.java b/server/src/com/cloud/api/query/vo/AffinityGroupJoinVO.java new file mode 100644 index 00000000000..e68996c53df --- /dev/null +++ b/server/src/com/cloud/api/query/vo/AffinityGroupJoinVO.java @@ -0,0 +1,248 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.api.query.vo; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; + +import com.cloud.vm.VirtualMachine; + +@Entity +@Table(name = "affinity_group_view") +public class AffinityGroupJoinVO extends BaseViewVO implements ControlledViewEntity { + + @Id + @Column(name="id", updatable=false, nullable = false) + private long id; + + @Column(name="name") + private String name; + + @Column(name = "description") + private String description; + + @Column(name = "uuid") + private String uuid; + + @Column(name="account_id") + private long accountId; + + @Column(name="account_uuid") + private String accountUuid; + + @Column(name="account_name") + private String accountName = null; + + @Column(name="account_type") + private short accountType; + + @Column(name="domain_id") + private long domainId; + + @Column(name="domain_uuid") + private String domainUuid; + + @Column(name="domain_name") + private String domainName = null; + + @Column(name="domain_path") + private String domainPath = null; + + @Column(name = "vm_id") + private long vmId; + + @Column(name = "vm_uuid") + private String vmUuid; + + @Column(name = "vm_name") + private String vmName; + + @Column(name = "vm_display_name") + private String vmDisplayName; + + @Column(name = "vm_state") + @Enumerated(value = EnumType.STRING) + protected VirtualMachine.State vmState = null; + + + public AffinityGroupJoinVO() { + } + + @Override + public long getId() { + return id; + } + + @Override + public void setId(long id) { + this.id = id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + @Override + public String getAccountUuid() { + return accountUuid; + } + + public void setAccountUuid(String accountUuid) { + this.accountUuid = accountUuid; + } + + @Override + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + @Override + public short getAccountType() { + return accountType; + } + + public void setAccountType(short accountType) { + this.accountType = accountType; + } + + @Override + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + @Override + public String getDomainUuid() { + return domainUuid; + } + + public void setDomainUuid(String domainUuid) { + this.domainUuid = domainUuid; + } + + @Override + public String getDomainName() { + return domainName; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + @Override + public String getDomainPath() { + return domainPath; + } + + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public long getVmId() { + return vmId; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getVmDisplayName() { + return vmDisplayName; + } + + public void setVmDisplayName(String vmDisplayName) { + this.vmDisplayName = vmDisplayName; + } + + public VirtualMachine.State getVmState() { + return vmState; + } + + public void setVmState(VirtualMachine.State vmState) { + this.vmState = vmState; + } + + @Override + public String getProjectUuid() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getProjectName() { + // TODO Auto-generated method stub + return null; + } +} diff --git a/server/src/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/com/cloud/api/query/vo/UserVmJoinVO.java index 33c49cdeae9..8d1314eafae 100644 --- a/server/src/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/com/cloud/api/query/vo/UserVmJoinVO.java @@ -368,6 +368,18 @@ public class UserVmJoinVO extends BaseViewVO implements ControlledViewEntity { @Column(name="tag_customer") private String tagCustomer; + @Column(name = "affinity_group_id") + private long affinityGroupId; + + @Column(name = "affinity_group_uuid") + private String affinityGroupUuid; + + @Column(name = "affinity_group_name") + private String affinityGroupName; + + @Column(name = "affinity_group_description") + private String affinityGroupDescription; + transient String password; @Transient @@ -1671,4 +1683,29 @@ public class UserVmJoinVO extends BaseViewVO implements ControlledViewEntity { public void setIp6Cidr(String ip6Cidr) { this.ip6Cidr = ip6Cidr; } + + + public long getAffinityGroupId() { + return affinityGroupId; + } + + + + public String getAffinityGroupUuid() { + return affinityGroupUuid; + } + + + + public String getAffinityGroupName() { + return affinityGroupName; + } + + + + public String getAffinityGroupDescription() { + return affinityGroupDescription; + } + + } diff --git a/server/src/com/cloud/configuration/ConfigurationManager.java b/server/src/com/cloud/configuration/ConfigurationManager.java index 20e98845ac0..c5f65e9d0fc 100644 --- a/server/src/com/cloud/configuration/ConfigurationManager.java +++ b/server/src/com/cloud/configuration/ConfigurationManager.java @@ -78,10 +78,11 @@ public interface ConfigurationManager extends ConfigurationService, Manager { * TODO * @param id * @param useVirtualNetwork + * @param deploymentPlanner * @return ID */ ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, - boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate); + boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner); /** * Creates a new disk offering diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 1526fb0e125..ceeae1ee910 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -1821,16 +1821,16 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } return createServiceOffering(userId, cmd.getIsSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber.intValue(), memory.intValue(), cpuSpeed.intValue(), cmd.getDisplayText(), - localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(), cmd.getNetworkRate()); + localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner()); } @Override @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CREATE, eventDescription = "creating service offering") public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_type, String name, int cpu, int ramSize, int speed, String displayText, - boolean localStorageRequired, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate) { + boolean localStorageRequired, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner) { tags = cleanupTags(tags); ServiceOfferingVO offering = new ServiceOfferingVO(name, cpu, ramSize, speed, networkRate, null, offerHA, limitResourceUse, volatileVm, displayText, localStorageRequired, false, tags, isSystem, vm_type, - domainId, hostTag); + domainId, hostTag, deploymentPlanner); if ((offering = _serviceOfferingDao.persist(offering)) != null) { UserContext.current().setEventDetails("Service offering id=" + offering.getId()); diff --git a/server/src/com/cloud/deploy/DeploymentPlanningManager.java b/server/src/com/cloud/deploy/DeploymentPlanningManager.java new file mode 100644 index 00000000000..13f1c67d613 --- /dev/null +++ b/server/src/com/cloud/deploy/DeploymentPlanningManager.java @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.deploy; + +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.utils.component.Manager; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +public interface DeploymentPlanningManager extends Manager { + + /** + * Manages vm deployment stages: First Process Affinity/Anti-affinity - Call + * the chain of AffinityGroupProcessor adapters to set deploymentplan scope + * and exclude list Secondly, Call DeploymentPlanner - to use heuristics to + * find the best spot to place the vm/volume. Planner will drill down to the + * write set of clusters to look for placement based on various heuristics. + * Lastly, Call Allocators - Given a cluster, allocators matches the + * requirements to capabilities of the physical resource (host, storage + * pool). + * + * @throws AffinityConflictException + * + * + * + */ + DeployDestination planDeployment(VirtualMachineProfile vmProfile, DeploymentPlan plan, + ExcludeList avoids) throws InsufficientServerCapacityException, AffinityConflictException; +} diff --git a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java new file mode 100644 index 00000000000..92aac3775a1 --- /dev/null +++ b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.deploy; + +import java.util.List; + +import javax.ejb.Local; +import javax.inject.Inject; + +import org.apache.cloudstack.affinity.AffinityGroupProcessor; +import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.log4j.Logger; + +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.ManagerBase; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Local(value = { DeploymentPlanningManager.class }) +public class DeploymentPlanningManagerImpl extends ManagerBase implements DeploymentPlanningManager, Manager { + + private static final Logger s_logger = Logger.getLogger(DeploymentPlanningManagerImpl.class); + @Inject + protected UserVmDao _vmDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected AffinityGroupDao _affinityGroupDao; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + + @Inject + protected List _planners; + + @Inject + protected List _affinityProcessors; + + @Override + public DeployDestination planDeployment(VirtualMachineProfile vmProfile, + DeploymentPlan plan, ExcludeList avoids) throws InsufficientServerCapacityException, + AffinityConflictException { + + // call affinitygroup chain + VirtualMachine vm = vmProfile.getVirtualMachine(); + long vmGroupCount = _affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId()); + + if (vmGroupCount > 0) { + for (AffinityGroupProcessor processor : _affinityProcessors) { + processor.process(vmProfile, plan, avoids); + } + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Deploy avoids pods: " + avoids.getPodsToAvoid() + ", clusters: " + + avoids.getClustersToAvoid() + ", hosts: " + avoids.getHostsToAvoid()); + } + + // call planners + DeployDestination dest = null; + for (DeploymentPlanner planner : _planners) { + if (planner.canHandle(vmProfile, plan, avoids)) { + dest = planner.plan(vmProfile, plan, avoids); + } else { + continue; + } + if (dest != null) { + avoids.addHost(dest.getHost().getId()); + break; + } + + } + return dest; + } + +} diff --git a/server/src/com/cloud/deploy/FirstFitPlanner.java b/server/src/com/cloud/deploy/FirstFitPlanner.java index 2dffe70fb46..1647cf7dba9 100755 --- a/server/src/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/com/cloud/deploy/FirstFitPlanner.java @@ -161,38 +161,44 @@ public class FirstFitPlanner extends PlannerBase implements DeploymentPlanner { + hostIdSpecified); } HostVO host = _hostDao.findById(hostIdSpecified); - if (s_logger.isDebugEnabled()) { - if(host == null){ - s_logger.debug("The specified host cannot be found"); - }else{ + if (host == null) { + s_logger.debug("The specified host cannot be found"); + } else if (avoid.shouldAvoid(host)) { + s_logger.debug("The specified host is in avoid set"); + } else { + if (s_logger.isDebugEnabled()) { s_logger.debug("Looking for suitable pools for this host under zone: "+host.getDataCenterId() +", pod: "+ host.getPodId()+", cluster: "+ host.getClusterId()); } - } - //search for storage under the zone, pod, cluster of the host. - DataCenterDeployment lastPlan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), hostIdSpecified, plan.getPoolId(), null, plan.getReservationContext()); + // search for storage under the zone, pod, cluster of the host. + DataCenterDeployment lastPlan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), + host.getClusterId(), hostIdSpecified, plan.getPoolId(), null, plan.getReservationContext()); - Pair>, List> result = findSuitablePoolsForVolumes(vmProfile, lastPlan, avoid, HostAllocator.RETURN_UPTO_ALL); - Map> suitableVolumeStoragePools = result.first(); - List readyAndReusedVolumes = result.second(); + Pair>, List> result = findSuitablePoolsForVolumes(vmProfile, + lastPlan, avoid, HostAllocator.RETURN_UPTO_ALL); + Map> suitableVolumeStoragePools = result.first(); + List readyAndReusedVolumes = result.second(); - //choose the potential pool for this VM for this host - if(!suitableVolumeStoragePools.isEmpty()){ - List suitableHosts = new ArrayList(); - suitableHosts.add(host); + // choose the potential pool for this VM for this host + if (!suitableVolumeStoragePools.isEmpty()) { + List suitableHosts = new ArrayList(); + suitableHosts.add(host); - Pair> potentialResources = findPotentialDeploymentResources(suitableHosts, suitableVolumeStoragePools); - if(potentialResources != null){ - Pod pod = _podDao.findById(host.getPodId()); - Cluster cluster = _clusterDao.findById(host.getClusterId()); - Map storageVolMap = potentialResources.second(); - // remove the reused vol<->pool from destination, since we don't have to prepare this volume. - for(Volume vol : readyAndReusedVolumes){ - storageVolMap.remove(vol); + Pair> potentialResources = findPotentialDeploymentResources( + suitableHosts, suitableVolumeStoragePools); + if (potentialResources != null) { + Pod pod = _podDao.findById(host.getPodId()); + Cluster cluster = _clusterDao.findById(host.getClusterId()); + Map storageVolMap = potentialResources.second(); + // remove the reused vol<->pool from destination, since + // we don't have to prepare this volume. + for (Volume vol : readyAndReusedVolumes) { + storageVolMap.remove(vol); + } + DeployDestination dest = new DeployDestination(dc, pod, cluster, host, storageVolMap); + s_logger.debug("Returning Deployment Destination: " + dest); + return dest; } - DeployDestination dest = new DeployDestination(dc, pod, cluster, host, storageVolMap); - s_logger.debug("Returning Deployment Destination: "+ dest); - return dest; } } s_logger.debug("Cannnot deploy to specified host, returning."); diff --git a/server/src/com/cloud/domain/dao/DomainDaoImpl.java b/server/src/com/cloud/domain/dao/DomainDaoImpl.java index 79ef17ed2a6..c30ca5ef49a 100644 --- a/server/src/com/cloud/domain/dao/DomainDaoImpl.java +++ b/server/src/com/cloud/domain/dao/DomainDaoImpl.java @@ -262,11 +262,14 @@ public class DomainDaoImpl extends GenericDaoBase implements Dom public Set getDomainParentIds(long domainId) { Set parentDomains = new HashSet(); Domain domain = findById(domainId); - parentDomains.add(domain.getId()); - - while (domain.getParent() != null) { - domain = findById(domain.getParent()); + + if (domain != null) { parentDomains.add(domain.getId()); + + while (domain.getParent() != null) { + domain = findById(domain.getParent()); + parentDomains.add(domain.getId()); + } } return parentDomains; diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index 53df3b970a8..596387f100e 100755 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -148,8 +148,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio @DB public void persistDefaultValues() throws InternalErrorException { - fixupScriptFileAttribute(); - // Create system user and admin user saveUser(); @@ -701,24 +699,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio } - private void fixupScriptFileAttribute() { - // TODO : this is a hacking fix to workaround that executable bit is not preserved in WAR package - String scriptPath = Script.findScript("", "scripts/vm/systemvm/injectkeys.sh"); - if(scriptPath != null) { - File file = new File(scriptPath); - if(!file.canExecute()) { - s_logger.info("Some of the shell script files may not have executable bit set. Fixup..."); - - String cmd = "sudo chmod ugo+x " + scriptPath; - s_logger.info("Executing " + cmd); - String result = Script.runSimpleBashScript(cmd); - if (result != null) { - s_logger.warn("Failed to fixup shell script executable bits " + result); - } - } - } - } - private void updateKeyPairsOnDisk(String homeDir) { File keyDir = new File(homeDir + "/.ssh"); Boolean devel = Boolean.valueOf(_configDao.getValue("developer")); @@ -749,7 +729,7 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio if (systemVmIsoPath == null) { throw new CloudRuntimeException("Unable to find systemvm iso vms/systemvm.iso"); } - final Script command = new Script(scriptPath, s_logger); + final Script command = new Script("/bin/bash " + scriptPath, s_logger); command.add(publicKeyPath); command.add(privKeyPath); command.add(systemVmIsoPath); diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 9ffea9c81bd..8dc27aebae8 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -44,6 +44,8 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.affinity.AffinityGroupProcessor; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; @@ -188,6 +190,11 @@ import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd; import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd; import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd; +import org.apache.cloudstack.api.command.user.affinitygroup.CreateAffinityGroupCmd; +import org.apache.cloudstack.api.command.user.affinitygroup.DeleteAffinityGroupCmd; +import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupTypesCmd; +import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupsCmd; +import org.apache.cloudstack.api.command.user.affinitygroup.UpdateVMAffinityGroupCmd; import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScalePolicyCmd; import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmGroupCmd; import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmProfileCmd; @@ -431,6 +438,7 @@ import com.cloud.dc.dao.HostPodDao; import com.cloud.dc.dao.PodVlanMapDao; import com.cloud.dc.dao.VlanDao; import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; @@ -687,9 +695,18 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe private List _userAuthenticators; private List _userPasswordEncoders; + @Inject + protected List _planners; + @Inject ClusterManager _clusterMgr; private String _hashKey = null; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + + @Inject + protected List _affinityProcessors; + public ManagementServerImpl() { setRunLevel(ComponentLifecycle.RUN_LEVEL_APPLICATION_MAINLOOP); } @@ -1048,6 +1065,15 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe ExcludeList excludes = new ExcludeList(); excludes.addHost(srcHostId); + // call affinitygroup chain + long vmGroupCount = _affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId()); + + if (vmGroupCount > 0) { + for (AffinityGroupProcessor processor : _affinityProcessors) { + processor.process(vmProfile, plan, excludes); + } + } + for (HostAllocator allocator : _hostAllocators) { suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, HostAllocator.RETURN_UPTO_ALL, false); if (suitableHosts != null && !suitableHosts.isEmpty()) { @@ -2534,6 +2560,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ConfigureInternalLoadBalancerElementCmd.class); cmdList.add(CreateInternalLoadBalancerElementCmd.class); cmdList.add(ListInternalLoadBalancerElementsCmd.class); + cmdList.add(CreateAffinityGroupCmd.class); + cmdList.add(DeleteAffinityGroupCmd.class); + cmdList.add(ListAffinityGroupsCmd.class); + cmdList.add(UpdateVMAffinityGroupCmd.class); + cmdList.add(ListAffinityGroupTypesCmd.class); return cmdList; } @@ -3599,4 +3630,14 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } + @Override + public List listDeploymentPlanners() { + List plannersAvailable = new ArrayList(); + for (DeploymentPlanner planner : _planners) { + plannersAvailable.add(planner.getName()); + } + + return plannersAvailable; + } + } diff --git a/server/src/com/cloud/service/ServiceOfferingVO.java b/server/src/com/cloud/service/ServiceOfferingVO.java index 7be939c3a15..a3da904688c 100755 --- a/server/src/com/cloud/service/ServiceOfferingVO.java +++ b/server/src/com/cloud/service/ServiceOfferingVO.java @@ -34,25 +34,25 @@ import com.cloud.vm.VirtualMachine; public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering { @Column(name="cpu") private int cpu; - + @Column(name="speed") private int speed; - + @Column(name="ram_size") private int ramSize; - + @Column(name="nw_rate") private Integer rateMbps; - + @Column(name="mc_rate") private Integer multicastRateMbps; - + @Column(name="ha_enabled") private boolean offerHA; @Column(name="limit_cpu_use") - private boolean limitCpuUse; - + private boolean limitCpuUse; + @Column(name="is_volatile") private boolean volatileVm; @@ -64,10 +64,13 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering @Column(name="vm_type") private String vm_type; - + @Column(name="sort_key") int sortKey; - + + @Column(name = "deployment_planner") + private String deploymentPlanner = "FirstFitPlanner"; + protected ServiceOfferingVO() { super(); } @@ -80,10 +83,11 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering this.rateMbps = rateMbps; this.multicastRateMbps = multicastRateMbps; this.offerHA = offerHA; - this.limitCpuUse = false; + this.limitCpuUse = false; this.volatileVm = false; this.default_use = defaultUse; this.vm_type = vm_type == null ? null : vm_type.toString().toLowerCase(); + this.deploymentPlanner = "FirstFitPlanner"; } public ServiceOfferingVO(String name, int cpu, int ramSize, int speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitCpuUse, boolean volatileVm, String displayText, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vm_type, Long domainId) { @@ -94,39 +98,54 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering this.rateMbps = rateMbps; this.multicastRateMbps = multicastRateMbps; this.offerHA = offerHA; - this.limitCpuUse = limitCpuUse; + this.limitCpuUse = limitCpuUse; this.volatileVm = volatileVm; this.vm_type = vm_type == null ? null : vm_type.toString().toLowerCase(); + this.deploymentPlanner = "FirstFitPlanner"; } public ServiceOfferingVO(String name, int cpu, int ramSize, int speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String displayText, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vm_type, Long domainId, String hostTag) { this(name, cpu, ramSize, speed, rateMbps, multicastRateMbps, offerHA, limitResourceUse, volatileVm, displayText, useLocalStorage, recreatable, tags, systemUse, vm_type, domainId); this.hostTag = hostTag; - } + this.deploymentPlanner = "FirstFitPlanner"; + } + + public ServiceOfferingVO(String name, int cpu, int ramSize, int speed, Integer rateMbps, Integer multicastRateMbps, + boolean offerHA, boolean limitResourceUse, boolean volatileVm, String displayText, boolean useLocalStorage, + boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vm_type, Long domainId, + String hostTag, String deploymentPlanner) { + this(name, cpu, ramSize, speed, rateMbps, multicastRateMbps, offerHA, limitResourceUse, volatileVm, + displayText, useLocalStorage, recreatable, tags, systemUse, vm_type, domainId, hostTag); + if (deploymentPlanner != null) { + this.deploymentPlanner = deploymentPlanner; + } else { + this.deploymentPlanner = "FirstFitPlanner"; + } + } @Override public boolean getOfferHA() { return offerHA; } - + public void setOfferHA(boolean offerHA) { this.offerHA = offerHA; } - @Override + @Override public boolean getLimitCpuUse() { return limitCpuUse; } - + public void setLimitResourceUse(boolean limitCpuUse) { this.limitCpuUse = limitCpuUse; } - - @Override + + @Override public boolean getDefaultUse() { return default_use; } - + @Override @Transient public String[] getTagsArray() { @@ -134,15 +153,15 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering if (tags == null || tags.length() == 0) { return new String[0]; } - + return tags.split(","); } - + @Override public int getCpu() { return cpu; } - + public void setCpu(int cpu) { this.cpu = cpu; } @@ -154,17 +173,17 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public void setRamSize(int ramSize) { this.ramSize = ramSize; } - + @Override public int getSpeed() { return speed; } - + @Override public int getRamSize() { return ramSize; } - + public void setRateMbps(Integer rateMbps) { this.rateMbps = rateMbps; } @@ -177,7 +196,7 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public void setMulticastRateMbps(Integer multicastRateMbps) { this.multicastRateMbps = multicastRateMbps; } - + @Override public Integer getMulticastRateMbps() { return multicastRateMbps; @@ -185,12 +204,12 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public void setHostTag(String hostTag) { this.hostTag = hostTag; - } - + } + public String getHostTag() { return hostTag; } - + public String getSystemVmType(){ return vm_type; } @@ -202,10 +221,14 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public int getSortKey() { return sortKey; } - + @Override public boolean getVolatileVm() { return volatileVm; } + public String getDeploymentPlanner() { + return deploymentPlanner; + } + } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index bc93df8e756..fe714c530b9 100755 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -42,6 +42,7 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; import org.apache.cloudstack.api.command.admin.user.RegisterCmd; @@ -220,6 +221,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M @Inject private AutoScaleManager _autoscaleMgr; @Inject VolumeManager volumeMgr; + @Inject + private AffinityGroupDao _affinityGroupDao; private List _userAuthenticators; List _userPasswordEncoders; @@ -238,7 +241,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M public List getUserAuthenticators() { return _userAuthenticators; } - + public void setUserAuthenticators(List authenticators) { _userAuthenticators = authenticators; } @@ -623,6 +626,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M int numRemoved = _securityGroupDao.removeByAccountId(accountId); s_logger.info("deleteAccount: Deleted " + numRemoved + " network groups for account " + accountId); + // Cleanup affinity groups + int numAGRemoved = _affinityGroupDao.removeByAccountId(accountId); + s_logger.info("deleteAccount: Deleted " + numAGRemoved + " affinity groups for account " + accountId); + // Delete all the networks boolean networksDeleted = true; s_logger.debug("Deleting networks for account " + account.getId()); diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 1c3764a8391..d281e5b4dc8 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -35,6 +35,9 @@ import javax.naming.ConfigurationException; import com.cloud.api.ApiDBUtils; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.affinity.AffinityGroupVO; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.*; @@ -369,7 +372,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use VpcManager _vpcMgr; @Inject TemplateManager templateMgr; - @Inject + @Inject protected GuestOSCategoryDao _guestOSCategoryDao; @Inject UsageEventDao _usageEventDao; @@ -378,6 +381,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use @Inject protected VMSnapshotManager _vmSnapshotMgr; + @Inject + AffinityGroupVMMapDao _affinityGroupVMMapDao; + @Inject + AffinityGroupDao _affinityGroupDao; + @Inject List plannerSelectors; @@ -684,7 +692,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use try { VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); - status = vmEntity.stop(new Long(userId).toString()); + status = vmEntity.stop(new Long(userId).toString()); } catch (ResourceUnavailableException e) { s_logger.debug("Unable to stop due to ", e); status = false; @@ -1916,7 +1924,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use @Override public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, - String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, String keyboard) + String hostName, + String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, + String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = UserContext.current().getCaller(); @@ -1966,13 +1977,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, - diskSize, networkList, securityGroupIdList, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, keyboard); + diskSize, networkList, securityGroupIdList, group, userData, sshKeyPair, hypervisor, caller, + requestedIps, defaultIps, keyboard, affinityGroupIdList); } @Override public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, - String sshKeyPair, Map requestedIps, IpAddresses defaultIps, String keyboard) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, + String sshKeyPair, Map requestedIps, + IpAddresses defaultIps, String keyboard, List affinityGroupIdList) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + StorageUnavailableException, ResourceAllocationException { Account caller = UserContext.current().getCaller(); @@ -2018,7 +2033,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use isSecurityGroupEnabledNetworkUsed = true; } else { - // Verify that all the networks are Shared/Guest; can't create combination of SG enabled and disabled networks + // Verify that all the networks are Shared/Guest; can't create combination of SG enabled and disabled networks for (Long networkId : networkIdList) { NetworkVO network = _networkDao.findById(networkId); @@ -2034,7 +2049,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } isSecurityGroupEnabledNetworkUsed = true; - } + } if (!(network.getTrafficType() == TrafficType.Guest && network.getGuestType() == Network.GuestType.Shared)) { throw new InvalidParameterValueException("Can specify only Shared Guest networks when" + @@ -2079,12 +2094,15 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, - diskSize, networkList, securityGroupIdList, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, keyboard); + diskSize, networkList, securityGroupIdList, group, userData, sshKeyPair, hypervisor, caller, + requestedIps, defaultIps, keyboard, affinityGroupIdList); } @Override public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, - String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, String keyboard) + String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, + String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = UserContext.current().getCaller(); @@ -2192,7 +2210,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } - return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, keyboard); + return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, + diskSize, networkList, null, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, + keyboard, affinityGroupIdList); } @@ -2205,7 +2225,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use @DB @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, String hostName, String displayName, Account owner, Long diskOfferingId, - Long diskSize, List networkList, List securityGroupIdList, String group, String userData, String sshKeyPair, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, String keyboard) + Long diskSize, List networkList, List securityGroupIdList, String group, String userData, + String sshKeyPair, HypervisorType hypervisor, Account caller, Map requestedIps, + IpAddresses defaultIps, String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException { _accountMgr.checkAccess(caller, null, true, owner); @@ -2261,6 +2283,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } + // check that the affinity groups exist + if (affinityGroupIdList != null) { + for (Long affinityGroupId : affinityGroupIdList) { + AffinityGroupVO ag = _affinityGroupDao.findById(affinityGroupId); + if (ag == null) { + throw new InvalidParameterValueException("Unable to find affinity group by id " + affinityGroupId); + } else { + // verify permissions + _accountMgr.checkAccess(caller, null, true, owner, ag); + } + } + } + if (template.getHypervisorType() != null && template.getHypervisorType() != HypervisorType.BareMetal) { // check if we have available pools for vm deployment long availablePools = _storagePoolDao.countPoolsByStatus(StoragePoolStatus.Up); @@ -2438,7 +2473,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use // * verify that there are no duplicates if (hostNames.contains(hostName)) { throw new InvalidParameterValueException("The vm with hostName " + hostName - + " already exists in the network domain: " + ntwkDomain + "; network=" + + " already exists in the network domain: " + ntwkDomain + "; network=" + _networkModel.getNetwork(ntwkId)); } } @@ -2510,7 +2545,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use List computeTags = new ArrayList(); computeTags.add(offering.getHostTag()); - List rootDiskTags = new ArrayList(); + List rootDiskTags = new ArrayList(); rootDiskTags.add(offering.getTags()); if(isIso){ @@ -2552,6 +2587,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use _securityGroupMgr.addInstanceToGroups(vm.getId(), securityGroupIdList); + if (affinityGroupIdList != null && !affinityGroupIdList.isEmpty()) { + _affinityGroupVMMapDao.updateMap(vm.getId(), affinityGroupIdList); + } + return vm; } @@ -2829,7 +2868,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use try { VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); - vmEntity.stop(new Long(userId).toString()); + vmEntity.stop(new Long(userId).toString()); } catch (ResourceUnavailableException e) { throw new CloudRuntimeException( "Unable to contact the agent to stop the virtual machine " @@ -3044,7 +3083,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use try { VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); - status = vmEntity.destroy(new Long(userId).toString()); + status = vmEntity.destroy(new Long(userId).toString()); } catch (CloudException e) { CloudRuntimeException ex = new CloudRuntimeException( "Unable to destroy with specified vmId", e); diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index af2271660fa..40725314f46 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -5,7 +5,7 @@ // to you 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 -// +// // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, @@ -68,7 +68,9 @@ import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.AffinityConflictException; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ConnectionException; @@ -226,8 +228,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject protected ResourceManager _resourceMgr; - - @Inject + + @Inject protected VMSnapshotManager _vmSnapshotMgr = null; @Inject protected ClusterDetailsDao _clusterDetailsDao; @@ -239,6 +241,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject VolumeManager volumeMgr; + @Inject + DeploymentPlanningManager _dpMgr; + Map> _vmGurus = new HashMap>(); protected StateMachine2 _stateMachine; @@ -586,7 +591,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac ConcurrentOperationException, ResourceUnavailableException { return advanceStart(vm, params, caller, account, null); } - + @Override public T advanceStart(T vm, Map params, User caller, Account account, DeploymentPlan planToDeploy) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -695,17 +700,13 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, template, offering, account, params); DeployDestination dest = null; - for (DeploymentPlanner planner : _planners) { - if (planner.canHandle(vmProfile, plan, avoids)) { - dest = planner.plan(vmProfile, plan, avoids); - } else { - continue; - } - if (dest != null) { - avoids.addHost(dest.getHost().getId()); - journal.record("Deployment found ", vmProfile, dest); - break; - } + try { + dest = _dpMgr.planDeployment(vmProfile, plan, avoids); + } catch (AffinityConflictException e2) { + s_logger.warn("Unable to create deployment, affinity rules associted to the VM conflict", e2); + throw new CloudRuntimeException( + "Unable to create deployment, affinity rules associted to the VM conflict"); + } if (dest == null) { @@ -719,6 +720,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId()); } + if (dest != null) { + avoids.addHost(dest.getHost().getId()); + journal.record("Deployment found ", vmProfile, dest); + } + long destHostId = dest.getHost().getId(); vm.setPodId(dest.getPod().getId()); Long cluster_id = dest.getCluster().getId(); @@ -739,7 +745,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac if (s_logger.isDebugEnabled()) { s_logger.debug("VM is being created in podId: " + vm.getPodIdToDeployIn()); } - _networkMgr.prepare(vmProfile, dest, ctx); + _networkMgr.prepare(vmProfile, dest, ctx); if (vm.getHypervisorType() != HypervisorType.BareMetal) { this.volumeMgr.prepare(vmProfile, dest); } @@ -797,7 +803,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac StopCommand cmd = new StopCommand(vm); StopAnswer answer = (StopAnswer) _agentMgr.easySend(destHostId, cmd); if (answer == null || !answer.getResult()) { - s_logger.warn("Unable to stop " + vm + " due to " + (answer != null ? answer.getDetails() : "no answers")); + s_logger.warn("Unable to stop " + vm + " due to " + (answer != null ? answer.getDetails() : "no answers")); _haMgr.scheduleStop(vm, destHostId, WorkType.ForceStop); throw new ExecutionException("Unable to stop " + vm + " so we are unable to retry the start operation"); } @@ -1144,7 +1150,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.error("State transit with event: " + e + " failed due to: " + vm.getInstanceName() + " has active VM snapshots tasks"); return false; } - + State oldState = vm.getState(); if (oldState == State.Starting) { if (e == Event.OperationSucceeded) { @@ -1179,12 +1185,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.debug("Unable to stop " + vm); return false; } - + if (!_vmSnapshotMgr.deleteAllVMSnapshots(vm.getId(),null)){ s_logger.debug("Unable to delete all snapshots for " + vm); return false; } - + try { if (!stateTransitTo(vm, VirtualMachine.Event.DestroyRequested, vm.getHostId())) { s_logger.debug("Unable to destroy the vm because it is not in the correct state: " + vm); @@ -1487,25 +1493,23 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac DeployDestination dest = null; while (true) { - for (DeploymentPlanner planner : _planners) { - if (planner.canHandle(profile, plan, excludes)) { - dest = planner.plan(profile, plan, excludes); - } else { - continue; - } - if (dest != null) { - if (s_logger.isDebugEnabled()) { - s_logger.debug("Planner " + planner + " found " + dest + " for migrating to."); - } - break; - } - if (s_logger.isDebugEnabled()) { - s_logger.debug("Planner " + planner + " was unable to find anything."); - } + try { + dest = _dpMgr.planDeployment(profile, plan, excludes); + } catch (AffinityConflictException e2) { + s_logger.warn("Unable to create deployment, affinity rules associted to the VM conflict", e2); + throw new CloudRuntimeException( + "Unable to create deployment, affinity rules associted to the VM conflict"); } - if (dest == null) { + if (dest != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Found destination " + dest + " for migrating to."); + } + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Unable to find destination for migrating the vm " + profile); + } throw new InsufficientServerCapacityException("Unable to find a server to migrate to.", host.getClusterId()); } @@ -1559,7 +1563,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Override public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - boolean isMachineUpgradable = true; + boolean isMachineUpgradable = true; for(HostAllocator allocator : _hostAllocators) { isMachineUpgradable = allocator.isVirtualMachineUpgradable(vm, offering); if(isMachineUpgradable) @@ -1628,7 +1632,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac return new StopCommand(vmName); } - public Commands fullHostSync(final long hostId, StartupRoutingCommand startup) { + public Commands fullHostSync(final long hostId, StartupRoutingCommand startup) { Commands commands = new Commands(OnError.Continue); Map infos = convertToInfos(startup); @@ -1637,7 +1641,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.debug("Found " + vms.size() + " VMs for host " + hostId); for (VMInstanceVO vm : vms) { AgentVmInfo info = infos.remove(vm.getId()); - + // sync VM Snapshots related transient states List vmSnapshotsInTrasientStates = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging,VMSnapshot.State.Reverting, VMSnapshot.State.Creating); if(vmSnapshotsInTrasientStates.size() > 1){ @@ -1649,11 +1653,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.info("Successfully sync VM with transient snapshot: " + vm.getInstanceName()); } } - + VMInstanceVO castedVm = null; if (info == null) { info = new AgentVmInfo(vm.getInstanceName(), getVmGuru(vm), vm, State.Stopped); - } + } castedVm = info.guru.findById(vm.getId()); HypervisorGuru hvGuru = _hvGuruMgr.getGuru(castedVm.getHypervisorType()); @@ -1767,7 +1771,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac for (VMInstanceVO vm : set_vms) { AgentVmInfo info = infos.remove(vm.getId()); VMInstanceVO castedVm = null; - + // sync VM Snapshots related transient states List vmSnapshotsInExpungingStates = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging, VMSnapshot.State.Creating,VMSnapshot.State.Reverting); if(vmSnapshotsInExpungingStates.size() > 0){ @@ -1785,9 +1789,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.info("Successfully sync VM with transient snapshot: " + vm.getInstanceName()); } } - - if ((info == null && (vm.getState() == State.Running || vm.getState() == State.Starting )) - || (info != null && (info.state == State.Running && vm.getState() == State.Starting))) + + if ((info == null && (vm.getState() == State.Running || vm.getState() == State.Starting )) + || (info != null && (info.state == State.Running && vm.getState() == State.Starting))) { s_logger.info("Found vm " + vm.getInstanceName() + " in inconsistent state. " + vm.getState() + " on CS while " + (info == null ? "Stopped" : "Running") + " on agent"); info = new AgentVmInfo(vm.getInstanceName(), getVmGuru(vm), vm, State.Stopped); @@ -1925,7 +1929,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac return map; } - protected Map convertToInfos(StartupRoutingCommand cmd) { + protected Map convertToInfos(StartupRoutingCommand cmd) { final Map states = cmd.getVmStates(); final HashMap map = new HashMap(); if (states == null) { @@ -1989,7 +1993,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac * compareState does as its name suggests and compares the states between * management server and agent. It returns whether something should be * cleaned up - * + * */ protected Command compareState(long hostId, VMInstanceVO vm, final AgentVmInfo info, final boolean fullSync, boolean trackExternalChange) { State agentState = info.state; @@ -2192,7 +2196,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac List nics = _nicsDao.listByVmId(profile.getId()); for (NicVO nic : nics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); - NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(profile.getHypervisorType(), network)); profile.addNic(nicProfile); } @@ -2319,7 +2323,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.fatal("The Cluster VM sync process failed for cluster id " + clusterId + " with ", e); } } - else { // for others KVM and VMWare + else { // for others KVM and VMWare StartupRoutingCommand startup = (StartupRoutingCommand) cmd; Commands commands = fullHostSync(agentId, startup); @@ -2492,7 +2496,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } @Override - public NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile requested) throws ConcurrentOperationException, + public NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile requested) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { s_logger.debug("Adding vm " + vm + " to network " + network + "; requested nic profile " + requested); @@ -2502,14 +2506,14 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } else { vmVO = _vmDao.findById(vm.getId()); } - ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(User.UID_SYSTEM), + ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(User.UID_SYSTEM), _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM)); - VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, + VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); DataCenter dc = _configMgr.getZone(network.getDataCenterId()); - Host host = _hostDao.findById(vm.getHostId()); + Host host = _hostDao.findById(vm.getHostId()); DeployDestination dest = new DeployDestination(dc, null, null, host); //check vm state @@ -2557,14 +2561,14 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac public boolean removeNicFromVm(VirtualMachine vm, NicVO nic) throws ConcurrentOperationException, ResourceUnavailableException { VMInstanceVO vmVO = _vmDao.findById(vm.getId()); NetworkVO network = _networkDao.findById(nic.getNetworkId()); - ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(User.UID_SYSTEM), + ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(User.UID_SYSTEM), _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM)); - VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, + VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); DataCenter dc = _configMgr.getZone(network.getDataCenterId()); - Host host = _hostDao.findById(vm.getHostId()); + Host host = _hostDao.findById(vm.getHostId()); DeployDestination dest = new DeployDestination(dc, null, null, host); VirtualMachineGuru vmGuru = getVmGuru(vmVO); HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); @@ -2576,9 +2580,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new CloudRuntimeException("Failed to remove nic from " + vm + " in " + network + ", nic is default."); } - NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), - _networkModel.getNetworkRate(network.getId(), vm.getId()), - _networkModel.isSecurityGroupSupportedInNetwork(network), + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), + _networkModel.getNetworkRate(network.getId(), vm.getId()), + _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vmProfile.getVirtualMachine().getHypervisorType(), network)); //1) Unplug the nic @@ -2611,14 +2615,14 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Override public boolean removeVmFromNetwork(VirtualMachine vm, Network network, URI broadcastUri) throws ConcurrentOperationException, ResourceUnavailableException { VMInstanceVO vmVO = _vmDao.findById(vm.getId()); - ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(User.UID_SYSTEM), + ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(User.UID_SYSTEM), _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM)); - VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, + VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); DataCenter dc = _configMgr.getZone(network.getDataCenterId()); - Host host = _hostDao.findById(vm.getHostId()); + Host host = _hostDao.findById(vm.getHostId()); DeployDestination dest = new DeployDestination(dc, null, null, host); VirtualMachineGuru vmGuru = getVmGuru(vmVO); HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); @@ -2643,9 +2647,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new CloudRuntimeException("Failed to remove nic from " + vm + " in " + network + ", nic is default."); } - NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), - _networkModel.getNetworkRate(network.getId(), vm.getId()), - _networkModel.isSecurityGroupSupportedInNetwork(network), + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), + _networkModel.getNetworkRate(network.getId(), vm.getId()), + _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vmProfile.getVirtualMachine().getHypervisorType(), network)); //1) Unplug the nic diff --git a/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java b/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java new file mode 100644 index 00000000000..2b490efe6e5 --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java @@ -0,0 +1,346 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.log4j.Logger; +import org.springframework.context.annotation.Primary; + + +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceInUseException; +import com.cloud.network.security.SecurityGroup; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.StateListener; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; + +@Local(value = { AffinityGroupService.class }) +public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGroupService, Manager, + StateListener { + + public static final Logger s_logger = Logger.getLogger(AffinityGroupServiceImpl.class); + private String _name; + + @Inject + AccountManager _accountMgr; + + @Inject + AffinityGroupDao _affinityGroupDao; + + @Inject + AffinityGroupVMMapDao _affinityGroupVMMapDao; + + @Inject + private UserVmDao _userVmDao; + + @Override + @ActionEvent(eventType = EventTypes.EVENT_AFFINITY_GROUP_CREATE, eventDescription = "Creating Affinity Group", create = true) + public AffinityGroup createAffinityGroup(String account, Long domainId, String affinityGroupName, + String affinityGroupType, String description) { + + Account caller = UserContext.current().getCaller(); + Account owner = _accountMgr.finalizeOwner(caller, account, domainId, null); + + if (_affinityGroupDao.isNameInUse(owner.getAccountId(), owner.getDomainId(), affinityGroupName)) { + throw new InvalidParameterValueException("Unable to create affinity group, a group with name " + + affinityGroupName + + " already exisits."); + } + + + //validate the affinityGroupType + Map typeProcessorMap = getAffinityTypeToProcessorMap(); + if (typeProcessorMap != null && !typeProcessorMap.isEmpty()) { + if (!typeProcessorMap.containsKey(affinityGroupType)) { + throw new InvalidParameterValueException("Unable to create affinity group, invalid affinity group type" + + affinityGroupType); + } + } else { + throw new InvalidParameterValueException( + "Unable to create affinity group, no Affinity Group Types configured"); + } + + if (domainId == null) { + domainId = owner.getDomainId(); + } + + AffinityGroupVO group = new AffinityGroupVO(affinityGroupName, affinityGroupType, description, domainId, + owner.getId()); + _affinityGroupDao.persist(group); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Created affinity group =" + affinityGroupName); + } + + return group; + } + + @DB + @Override + @ActionEvent(eventType = EventTypes.EVENT_AFFINITY_GROUP_DELETE, eventDescription = "Deleting affinity group") + public boolean deleteAffinityGroup(Long affinityGroupId, String account, Long domainId, String affinityGroupName) + throws ResourceInUseException { + + Account caller = UserContext.current().getCaller(); + Account owner = _accountMgr.finalizeOwner(caller, account, domainId, null); + + AffinityGroupVO group = null; + if (affinityGroupId != null) { + group = _affinityGroupDao.findById(affinityGroupId); + if (group == null) { + throw new InvalidParameterValueException("Unable to find affinity group: " + affinityGroupId + + "; failed to delete group."); + } + } else if (affinityGroupName != null) { + group = _affinityGroupDao.findByAccountAndName(owner.getAccountId(), affinityGroupName); + if (group == null) { + throw new InvalidParameterValueException("Unable to find affinity group: " + affinityGroupName + + "; failed to delete group."); + } + } else { + throw new InvalidParameterValueException( + "Either the affinity group Id or group name must be specified to delete the group"); + } + if (affinityGroupId == null) { + affinityGroupId = group.getId(); + } + // check permissions + _accountMgr.checkAccess(caller, null, true, group); + + final Transaction txn = Transaction.currentTxn(); + txn.start(); + + group = _affinityGroupDao.lockRow(affinityGroupId, true); + if (group == null) { + throw new InvalidParameterValueException("Unable to find affinity group by id " + affinityGroupId); + } + + List affinityGroupVmMap = _affinityGroupVMMapDao.listByAffinityGroup(affinityGroupId); + if (!affinityGroupVmMap.isEmpty()) { + throw new ResourceInUseException("Cannot delete affinity group when it's in use by virtual machines"); + } + + _affinityGroupDao.expunge(affinityGroupId); + txn.commit(); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Deleted affinity group id=" + affinityGroupId); + } + return true; + } + + @Override + public Pair, Integer> listAffinityGroups(Long affinityGroupId, String affinityGroupName, String affinityGroupType, Long vmId, Long startIndex, Long pageSize) { + Filter searchFilter = new Filter(AffinityGroupVO.class, "id", Boolean.TRUE, startIndex, pageSize); + + Account caller = UserContext.current().getCaller(); + + Long accountId = caller.getAccountId(); + Long domainId = caller.getDomainId(); + + SearchBuilder vmInstanceSearch = _affinityGroupVMMapDao.createSearchBuilder(); + vmInstanceSearch.and("instanceId", vmInstanceSearch.entity().getInstanceId(), SearchCriteria.Op.EQ); + + SearchBuilder groupSearch = _affinityGroupDao.createSearchBuilder(); + + SearchCriteria sc = groupSearch.create(); + + if (accountId != null) { + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + } + + if (domainId != null) { + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + } + + if (affinityGroupId != null) { + sc.addAnd("id", SearchCriteria.Op.EQ, affinityGroupId); + } + + if (affinityGroupName != null) { + sc.addAnd("name", SearchCriteria.Op.EQ, affinityGroupName); + } + + if (affinityGroupType != null) { + sc.addAnd("type", SearchCriteria.Op.EQ, affinityGroupType); + } + + if (vmId != null) { + UserVmVO userVM = _userVmDao.findById(vmId); + if (userVM == null) { + throw new InvalidParameterValueException("Unable to list affinity groups for virtual machine instance " + + vmId + "; instance not found."); + } + _accountMgr.checkAccess(caller, null, true, userVM); + // add join to affinity_groups_vm_map + groupSearch.join("vmInstanceSearch", vmInstanceSearch, groupSearch.entity().getId(), vmInstanceSearch + .entity().getAffinityGroupId(), JoinBuilder.JoinType.INNER); + sc.setJoinParameters("vmInstanceSearch", "instanceId", vmId); + } + + Pair, Integer> result = _affinityGroupDao.searchAndCount(sc, searchFilter); + return new Pair, Integer>(result.first(), result.second()); + } + + + @Override + public List listAffinityGroupTypes() { + List types = new ArrayList(); + Map componentMap = ComponentContext.getComponentsOfType(AffinityGroupProcessor.class); + + if (componentMap.size() > 0) { + for (Entry entry : componentMap.entrySet()) { + types.add(entry.getValue().getType()); + } + + } + return types; + } + + protected Map getAffinityTypeToProcessorMap() { + Map typeProcessorMap = new HashMap(); + Map componentMap = ComponentContext + .getComponentsOfType(AffinityGroupProcessor.class); + + if (componentMap.size() > 0) { + for (Entry entry : componentMap.entrySet()) { + typeProcessorMap.put(entry.getValue().getType(), entry.getValue()); + } + } + return typeProcessorMap; + } + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + _name = name; + VirtualMachine.State.getStateMachine().registerListener(this); + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return _name; + } + + @Override + public AffinityGroup getAffinityGroup(Long groupId) { + return _affinityGroupDao.findById(groupId); + } + + @Override + public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, + boolean status, Object opaque) { + return true; + } + + @Override + public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, + boolean status, Object opaque) { + if (!status) { + return false; + } + if ((newState == State.Expunging)) { + // cleanup all affinity groups associations of the Expunged VM + SearchCriteria sc = _affinityGroupVMMapDao.createSearchCriteria(); + sc.addAnd("instanceId", SearchCriteria.Op.EQ, vo.getId()); + _affinityGroupVMMapDao.expunge(sc); + } + return true; + } + + @Override + public UserVm updateVMAffinityGroups(Long vmId, List affinityGroupIds) { + // Verify input parameters + UserVmVO vmInstance = _userVmDao.findById(vmId); + if (vmInstance == null) { + throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); + } + + // Check that the VM is stopped + if (!vmInstance.getState().equals(State.Stopped)) { + s_logger.warn("Unable to update affinity groups of the virtual machine " + vmInstance.toString() + + " in state " + vmInstance.getState()); + throw new InvalidParameterValueException("Unable update affinity groups of the virtual machine " + + vmInstance.toString() + " " + "in state " + vmInstance.getState() + + "; make sure the virtual machine is stopped and not in an error state before updating."); + } + + Account caller = UserContext.current().getCaller(); + Account owner = _accountMgr.getAccount(vmInstance.getAccountId()); + + // check that the affinity groups exist + for (Long affinityGroupId : affinityGroupIds) { + AffinityGroupVO ag = _affinityGroupDao.findById(affinityGroupId); + if (ag == null) { + throw new InvalidParameterValueException("Unable to find affinity group by id " + affinityGroupId); + } else { + // verify permissions + _accountMgr.checkAccess(caller, null, true, owner, ag); + } + } + _affinityGroupVMMapDao.updateMap(vmId, affinityGroupIds); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Updated VM :" + vmId + " affinity groups to =" + affinityGroupIds); + } + // APIResponseHelper will pull out the updated affinitygroups. + return vmInstance; + + } + +} diff --git a/server/src/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java b/server/src/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java new file mode 100644 index 00000000000..f84e4c351aa --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.SecondaryTables; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = ("affinity_group_vm_map")) +public class AffinityGroupVMMapVO implements InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "affinity_group_id") + private long affinityGroupId; + + @Column(name = "instance_id") + private long instanceId; + + public AffinityGroupVMMapVO() { + } + + public AffinityGroupVMMapVO(long affinityGroupId, long instanceId) { + this.affinityGroupId = affinityGroupId; + this.instanceId = instanceId; + } + + public long getId() { + return id; + } + + public long getAffinityGroupId() { + return affinityGroupId; + } + + + public long getInstanceId() { + return instanceId; + } + +} diff --git a/server/src/org/apache/cloudstack/affinity/AffinityGroupVO.java b/server/src/org/apache/cloudstack/affinity/AffinityGroupVO.java new file mode 100644 index 00000000000..b6c4a027484 --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/AffinityGroupVO.java @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + + +@Entity +@Table(name = ("affinity_group")) +public class AffinityGroupVO implements AffinityGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "name") + private String name; + + @Column(name = "type") + private String type; + + @Column(name = "description") + private String description; + + @Column(name = "domain_id") + private long domainId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "uuid") + private String uuid; + + public AffinityGroupVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public AffinityGroupVO(String name, String type, String description, long domainId, long accountId) { + this.name = name; + this.description = description; + this.domainId = domainId; + this.accountId = accountId; + this.uuid = UUID.randomUUID().toString(); + this.type = type; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public long getDomainId() { + return domainId; + } + + @Override + public long getAccountId() { + return accountId; + } + + @Override + public String getUuid() { + return this.uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getType() { + return type; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder("AffinityGroup["); + buf.append(id).append("|").append(name).append("|").append(type).append("]"); + return buf.toString(); + } + +} diff --git a/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java new file mode 100644 index 00000000000..296e7b1d043 --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity.dao; + +import java.util.List; + +import org.apache.cloudstack.affinity.AffinityGroupVO; +import com.cloud.utils.db.GenericDao; + +public interface AffinityGroupDao extends GenericDao { + List listByAccountId(long accountId); + boolean isNameInUse(Long accountId, Long domainId, String name); + AffinityGroupVO findByAccountAndName(Long accountId, String name); + List findByAccountAndNames(Long accountId, String... names); + int removeByAccountId(long accountId); +} diff --git a/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java new file mode 100644 index 00000000000..d189d609ff2 --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity.dao; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.ejb.Local; +import org.apache.cloudstack.affinity.AffinityGroupVO; +import org.springframework.stereotype.Component; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Local(value = { AffinityGroupDao.class }) +public class AffinityGroupDaoImpl extends GenericDaoBase implements AffinityGroupDao { + private SearchBuilder AccountIdSearch; + private SearchBuilder AccountIdNameSearch; + private SearchBuilder AccountIdNamesSearch; + + + public AffinityGroupDaoImpl() { + + } + + @PostConstruct + protected void init() { + AccountIdSearch = createSearchBuilder(); + AccountIdSearch.and("accountId", AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountIdSearch.done(); + + AccountIdNameSearch = createSearchBuilder(); + AccountIdNameSearch.and("accountId", AccountIdNameSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountIdNameSearch.and("name", AccountIdNameSearch.entity().getName(), SearchCriteria.Op.EQ); + + AccountIdNamesSearch = createSearchBuilder(); + AccountIdNamesSearch.and("accountId", AccountIdNamesSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountIdNamesSearch.and("groupNames", AccountIdNamesSearch.entity().getName(), SearchCriteria.Op.IN); + AccountIdNameSearch.done(); + } + + @Override + public List listByAccountId(long accountId) { + SearchCriteria sc = AccountIdSearch.create(); + sc.setParameters("accountId", accountId); + return listBy(sc); + } + + @Override + public boolean isNameInUse(Long accountId, Long domainId, String name) { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("name", SearchCriteria.Op.EQ, name); + if (accountId != null) { + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + } else { + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + sc.addAnd("accountId", SearchCriteria.Op.NULL); + } + + List AffinityGroups = listBy(sc); + return ((AffinityGroups != null) && !AffinityGroups.isEmpty()); + } + + @Override + public AffinityGroupVO findByAccountAndName(Long accountId, String name) { + SearchCriteria sc = AccountIdNameSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParameters("name", name); + + return findOneIncludingRemovedBy(sc); + } + + @Override + public List findByAccountAndNames(Long accountId, String... names) { + SearchCriteria sc = AccountIdNamesSearch.create(); + sc.setParameters("accountId", accountId); + + sc.setParameters("groupNames", (Object [])names); + + return listBy(sc); + } + @Override + public int removeByAccountId(long accountId) { + SearchCriteria sc = AccountIdSearch.create(); + sc.setParameters("accountId", accountId); + return expunge(sc); + } +} diff --git a/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDao.java b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDao.java new file mode 100644 index 00000000000..a98ae0ff7bd --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDao.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity.dao; + +import java.util.List; + +import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.VirtualMachine.State; + +public interface AffinityGroupVMMapDao extends GenericDao { + + List listByInstanceId(long instanceId); + + Pair, Integer> listByInstanceId(long instanceId, Filter filter); + + List listByAffinityGroup(long affinityGroupId); + + List listVmIdsByAffinityGroup(long affinityGroupId); + + AffinityGroupVMMapVO findByVmIdGroupId(long instanceId, long affinityGroupId); + + long countAffinityGroupsForVm(long instanceId); + + int deleteVM(long instanceId); + + AffinityGroupVMMapVO findByVmIdType(long instanceId, String type); + + void updateMap(Long vmId, List affinityGroupIds); +} diff --git a/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java new file mode 100644 index 00000000000..abc2a2bd59c --- /dev/null +++ b/server/src/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity.dao; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.ejb.Local; +import javax.inject.Inject; + +import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; +import org.apache.cloudstack.affinity.AffinityGroupVO; +import org.springframework.stereotype.Component; + +import com.cloud.host.HostTagVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.JoinBuilder.JoinType; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Func; +import com.cloud.utils.db.Transaction; + +@Local(value = { AffinityGroupVMMapDao.class }) +public class AffinityGroupVMMapDaoImpl extends GenericDaoBase implements + AffinityGroupVMMapDao { + private SearchBuilder ListByVmId; + private SearchBuilder ListByVmIdGroupId; + protected GenericSearchBuilder CountSGForVm; + private GenericSearchBuilder ListVmIdByAffinityGroup; + private SearchBuilder ListByAffinityGroup; + private SearchBuilder ListByVmIdType; + + @Inject + protected AffinityGroupDao _affinityGroupDao; + + public AffinityGroupVMMapDaoImpl() { + } + + @PostConstruct + protected void init() { + ListVmIdByAffinityGroup = createSearchBuilder(Long.class); + ListVmIdByAffinityGroup.and("affinityGroupId", ListVmIdByAffinityGroup.entity().getAffinityGroupId(), + SearchCriteria.Op.EQ); + ListVmIdByAffinityGroup.selectField(ListVmIdByAffinityGroup.entity().getInstanceId()); + ListVmIdByAffinityGroup.done(); + + ListByAffinityGroup = createSearchBuilder(); + ListByAffinityGroup.and("affinityGroupId", ListByAffinityGroup.entity().getAffinityGroupId(), + SearchCriteria.Op.EQ); + ListByAffinityGroup.done(); + + ListByVmId = createSearchBuilder(); + ListByVmId.and("instanceId", ListByVmId.entity().getInstanceId(), SearchCriteria.Op.EQ); + ListByVmId.done(); + + ListByVmIdGroupId = createSearchBuilder(); + ListByVmIdGroupId.and("instanceId", ListByVmIdGroupId.entity().getInstanceId(), SearchCriteria.Op.EQ); + ListByVmIdGroupId.and("affinityGroupId", ListByVmIdGroupId.entity().getAffinityGroupId(), SearchCriteria.Op.EQ); + ListByVmIdGroupId.done(); + + SearchBuilder groupSearch = _affinityGroupDao.createSearchBuilder(); + groupSearch.and("type", groupSearch.entity().getType(), SearchCriteria.Op.EQ); + + ListByVmIdType = createSearchBuilder(); + ListByVmIdType.and("instanceId", ListByVmIdType.entity().getInstanceId(), SearchCriteria.Op.EQ); + ListByVmIdType.join("groupSearch", groupSearch, ListByVmIdType.entity().getAffinityGroupId(), groupSearch.entity().getId(), JoinType.INNER); + ListByVmIdType.done(); + + CountSGForVm = createSearchBuilder(Long.class); + CountSGForVm.select(null, Func.COUNT, null); + CountSGForVm.and("vmId", CountSGForVm.entity().getInstanceId(), SearchCriteria.Op.EQ); + CountSGForVm.done(); + } + + @Override + public List listByAffinityGroup(long affinityGroupId) { + SearchCriteria sc = ListByAffinityGroup.create(); + sc.setParameters("affinityGroupId", affinityGroupId); + return listBy(sc); + } + + @Override + public List listByInstanceId(long vmId) { + SearchCriteria sc = ListByVmId.create(); + sc.setParameters("instanceId", vmId); + return listBy(sc); + } + + @Override + public Pair, Integer> listByInstanceId(long instanceId, Filter filter) { + SearchCriteria sc = ListByVmId.create(); + sc.setParameters("instanceId", instanceId); + return this.searchAndCount(sc, filter); + } + + @Override + public int deleteVM(long instanceId) { + SearchCriteria sc = ListByVmId.create(); + sc.setParameters("instanceId", instanceId); + return super.expunge(sc); + } + + @Override + public List listVmIdsByAffinityGroup(long affinityGroupId) { + SearchCriteria sc = ListVmIdByAffinityGroup.create(); + sc.setParameters("affinityGroupId", affinityGroupId); + return customSearchIncludingRemoved(sc, null); + } + + @Override + public AffinityGroupVMMapVO findByVmIdGroupId(long instanceId, long affinityGroupId) { + SearchCriteria sc = ListByVmIdGroupId.create(); + sc.setParameters("affinityGroupId", affinityGroupId); + sc.setParameters("instanceId", instanceId); + return findOneIncludingRemovedBy(sc); + } + + @Override + public long countAffinityGroupsForVm(long instanceId) { + SearchCriteria sc = CountSGForVm.create(); + sc.setParameters("vmId", instanceId); + return customSearch(sc, null).get(0); + } + + @Override + public AffinityGroupVMMapVO findByVmIdType(long instanceId, String type) { + SearchCriteria sc = ListByVmIdType.create(); + sc.setParameters("instanceId", instanceId); + sc.setJoinParameters("groupSearch", "type", type); + return findOneBy(sc); + } + + @Override + public void updateMap(Long vmId, List affinityGroupIds) { + Transaction txn = Transaction.currentTxn(); + txn.start(); + + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("instanceId", SearchCriteria.Op.EQ, vmId); + expunge(sc); + + for (Long groupId : affinityGroupIds) { + AffinityGroupVMMapVO vo = new AffinityGroupVMMapVO(groupId, vmId); + persist(vo); + } + + txn.commit(); + + } +} diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java index dd8dd83df58..fd826d9e86e 100644 --- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java +++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java @@ -253,13 +253,13 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, // TODO Auto-generated method stub return null; } - + @Override public UserVm removeNicFromVirtualMachine(RemoveNicFromVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException { // TODO Auto-generated method stub return null; } - + @Override public UserVm updateDefaultNicForVirtualMachine(UpdateDefaultNicForVMCmd cmd) throws InvalidParameterValueException, CloudRuntimeException { // TODO Auto-generated method stub @@ -330,7 +330,9 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, @Override public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, - IpAddresses defaultIp, String keyboard) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, + IpAddresses defaultIp, + String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { // TODO Auto-generated method stub return null; @@ -339,7 +341,9 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, @Override public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, - String sshKeyPair, Map requestedIps, IpAddresses defaultIps, String keyboard) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + String sshKeyPair, Map requestedIps, + IpAddresses defaultIps, String keyboard, List affinityGroupIdList) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { // TODO Auto-generated method stub return null; @@ -348,7 +352,9 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, @Override public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, - String keyboard) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { + String keyboard, List affinityGroupIdList) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, + ResourceAllocationException { // TODO Auto-generated method stub return null; } diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index d96e831cfeb..b0063fa2e2e 100644 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -433,7 +433,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu */ @Override public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, boolean offerHA, - boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate) { + boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner) { // TODO Auto-generated method stub return null; } diff --git a/server/test/org/apache/cloudstack/affinity/AffinityApiTestConfiguration.java b/server/test/org/apache/cloudstack/affinity/AffinityApiTestConfiguration.java new file mode 100644 index 00000000000..c6a0755744c --- /dev/null +++ b/server/test/org/apache/cloudstack/affinity/AffinityApiTestConfiguration.java @@ -0,0 +1,332 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import java.io.IOException; + +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import com.cloud.utils.component.ComponentContext; +import com.cloud.agent.AgentManager; +import com.cloud.alert.AlertManager; +import com.cloud.api.query.dao.UserAccountJoinDaoImpl; +import com.cloud.capacity.dao.CapacityDaoImpl; +import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.dao.AccountVlanMapDaoImpl; +import com.cloud.dc.dao.ClusterDaoImpl; +import com.cloud.dc.dao.DataCenterDaoImpl; +import com.cloud.dc.dao.DataCenterIpAddressDaoImpl; +import com.cloud.dc.dao.DataCenterLinkLocalIpAddressDaoImpl; +import com.cloud.dc.dao.DataCenterVnetDaoImpl; +import com.cloud.dc.dao.DcDetailsDaoImpl; +import com.cloud.dc.dao.HostPodDaoImpl; +import com.cloud.dc.dao.PodVlanDaoImpl; +import com.cloud.dc.dao.PodVlanMapDaoImpl; +import com.cloud.dc.dao.VlanDaoImpl; +import com.cloud.domain.dao.DomainDaoImpl; +import com.cloud.event.EventUtils; +import com.cloud.event.dao.EventDaoImpl; +import com.cloud.event.dao.UsageEventDaoImpl; +import com.cloud.host.dao.HostDaoImpl; +import com.cloud.host.dao.HostDetailsDaoImpl; +import com.cloud.host.dao.HostTagsDaoImpl; +import com.cloud.network.Ipv6AddressManager; +import com.cloud.network.NetworkManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; +import com.cloud.network.StorageNetworkManager; +import com.cloud.network.dao.FirewallRulesCidrsDaoImpl; +import com.cloud.network.dao.FirewallRulesDaoImpl; +import com.cloud.network.dao.IPAddressDaoImpl; +import com.cloud.network.dao.LoadBalancerDaoImpl; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDomainDaoImpl; +import com.cloud.network.dao.NetworkServiceMapDaoImpl; +import com.cloud.network.dao.PhysicalNetworkDaoImpl; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDaoImpl; +import com.cloud.network.dao.PhysicalNetworkTrafficTypeDaoImpl; +import com.cloud.network.dao.UserIpv6AddressDaoImpl; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.IpDeployer; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.rules.dao.PortForwardingRulesDaoImpl; +import com.cloud.network.vpc.NetworkACLManager; +import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpc.dao.PrivateIpDaoImpl; +import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.projects.ProjectManager; +import com.cloud.service.dao.ServiceOfferingDaoImpl; +import com.cloud.storage.dao.DiskOfferingDaoImpl; +import com.cloud.storage.dao.S3DaoImpl; +import com.cloud.storage.dao.SnapshotDaoImpl; +import com.cloud.storage.dao.StoragePoolDetailsDaoImpl; +import com.cloud.storage.dao.SwiftDaoImpl; +import com.cloud.storage.dao.VolumeDaoImpl; +import com.cloud.storage.s3.S3Manager; +import com.cloud.storage.secondary.SecondaryStorageVmManager; +import com.cloud.storage.swift.SwiftManager; +import com.cloud.tags.dao.ResourceTagsDaoImpl; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; +import com.cloud.user.UserContext; +import com.cloud.user.UserContextInitializer; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.AccountDaoImpl; +import com.cloud.user.dao.UserDaoImpl; +import com.cloud.utils.component.SpringComponentScanUtils; +import com.cloud.utils.db.GenericDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.dao.InstanceGroupDaoImpl; +import com.cloud.vm.dao.NicDaoImpl; +import com.cloud.vm.dao.NicSecondaryIpDaoImpl; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDaoImpl; + +@Configuration +@ComponentScan(basePackageClasses = { AccountVlanMapDaoImpl.class, VolumeDaoImpl.class, HostPodDaoImpl.class, + DomainDaoImpl.class, SwiftDaoImpl.class, ServiceOfferingDaoImpl.class, VlanDaoImpl.class, + IPAddressDaoImpl.class, ResourceTagsDaoImpl.class, InstanceGroupDaoImpl.class, + UserAccountJoinDaoImpl.class, CapacityDaoImpl.class, SnapshotDaoImpl.class, HostDaoImpl.class, + VMInstanceDaoImpl.class, HostTransferMapDaoImpl.class, PortForwardingRulesDaoImpl.class, + PrivateIpDaoImpl.class, UsageEventDaoImpl.class, PodVlanMapDaoImpl.class, DiskOfferingDaoImpl.class, + DataCenterDaoImpl.class, DataCenterIpAddressDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, + DataCenterVnetDaoImpl.class, PodVlanDaoImpl.class, DcDetailsDaoImpl.class, NicSecondaryIpDaoImpl.class, + UserIpv6AddressDaoImpl.class, S3DaoImpl.class, UserDaoImpl.class, NicDaoImpl.class, NetworkDomainDaoImpl.class, + HostDetailsDaoImpl.class, HostTagsDaoImpl.class, ClusterDaoImpl.class, FirewallRulesDaoImpl.class, + FirewallRulesCidrsDaoImpl.class, PhysicalNetworkDaoImpl.class, PhysicalNetworkTrafficTypeDaoImpl.class, + PhysicalNetworkServiceProviderDaoImpl.class, LoadBalancerDaoImpl.class, NetworkServiceMapDaoImpl.class, + PrimaryDataStoreDaoImpl.class, StoragePoolDetailsDaoImpl.class, AffinityGroupServiceImpl.class, + ComponentContext.class, AffinityGroupProcessor.class, UserVmVO.class, EventUtils.class, UserVmVO.class, + EventDaoImpl.class }, includeFilters = { @Filter(value = AffinityApiTestConfiguration.Library.class, type = FilterType.CUSTOM) }, useDefaultFilters = false) +public class AffinityApiTestConfiguration { + + @Bean + public AccountDao accountDao() { + return Mockito.mock(AccountDao.class); + } + + @Bean + public EventUtils eventUtils() { + return Mockito.mock(EventUtils.class); + } + + @Bean + public AffinityGroupProcessor affinityGroupProcessor() { + return Mockito.mock(AffinityGroupProcessor.class); + } + + @Bean + public ComponentContext componentContext() { + return Mockito.mock(ComponentContext.class); + } + + + @Bean + public UserContextInitializer userContextInitializer() { + return Mockito.mock(UserContextInitializer.class); + } + + @Bean + public UserVmVO userVmVO() { + return Mockito.mock(UserVmVO.class); + } + + @Bean + public AffinityGroupDao affinityGroupDao() { + return Mockito.mock(AffinityGroupDao.class); + } + + @Bean + public AffinityGroupVMMapDao affinityGroupVMMapDao() { + return Mockito.mock(AffinityGroupVMMapDao.class); + } + + @Bean + public AccountManager acctMgr() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public NetworkService ntwkSvc() { + return Mockito.mock(NetworkService.class); + } + + @Bean + public NetworkModel ntwkMdl() { + return Mockito.mock(NetworkModel.class); + } + + @Bean + public AlertManager alertMgr() { + return Mockito.mock(AlertManager.class); + } + + @Bean + public SecurityChecker securityChkr() { + return Mockito.mock(SecurityChecker.class); + } + + @Bean + public ResourceLimitService resourceSvc() { + return Mockito.mock(ResourceLimitService.class); + } + + @Bean + public ProjectManager projectMgr() { + return Mockito.mock(ProjectManager.class); + } + + @Bean + public SecondaryStorageVmManager ssvmMgr() { + return Mockito.mock(SecondaryStorageVmManager.class); + } + + @Bean + public SwiftManager swiftMgr() { + return Mockito.mock(SwiftManager.class); + } + + @Bean + public S3Manager s3Mgr() { + return Mockito.mock(S3Manager.class); + } + + @Bean + public VpcManager vpcMgr() { + return Mockito.mock(VpcManager.class); + } + + @Bean + public UserVmDao userVMDao() { + return Mockito.mock(UserVmDao.class); + } + + @Bean + public RulesManager rulesMgr() { + return Mockito.mock(RulesManager.class); + } + + @Bean + public LoadBalancingRulesManager lbRulesMgr() { + return Mockito.mock(LoadBalancingRulesManager.class); + } + + @Bean + public RemoteAccessVpnService vpnMgr() { + return Mockito.mock(RemoteAccessVpnService.class); + } + + @Bean + public NetworkGuru ntwkGuru() { + return Mockito.mock(NetworkGuru.class); + } + + @Bean + public NetworkElement ntwkElement() { + return Mockito.mock(NetworkElement.class); + } + + @Bean + public IpDeployer ipDeployer() { + return Mockito.mock(IpDeployer.class); + } + + @Bean + public DhcpServiceProvider dhcpProvider() { + return Mockito.mock(DhcpServiceProvider.class); + } + + @Bean + public FirewallManager firewallMgr() { + return Mockito.mock(FirewallManager.class); + } + + @Bean + public AgentManager agentMgr() { + return Mockito.mock(AgentManager.class); + } + + @Bean + public StorageNetworkManager storageNtwkMgr() { + return Mockito.mock(StorageNetworkManager.class); + } + + @Bean + public NetworkACLManager ntwkAclMgr() { + return Mockito.mock(NetworkACLManager.class); + } + + @Bean + public Ipv6AddressManager ipv6Mgr() { + return Mockito.mock(Ipv6AddressManager.class); + } + + @Bean + public ConfigurationDao configDao() { + return Mockito.mock(ConfigurationDao.class); + } + + @Bean + public NetworkManager networkManager() { + return Mockito.mock(NetworkManager.class); + } + + @Bean + public NetworkOfferingDao networkOfferingDao() { + return Mockito.mock(NetworkOfferingDao.class); + } + + @Bean + public NetworkDao networkDao() { + return Mockito.mock(NetworkDao.class); + } + + @Bean + public NetworkOfferingServiceMapDao networkOfferingServiceMapDao() { + return Mockito.mock(NetworkOfferingServiceMapDao.class); + } + + public static class Library implements TypeFilter { + + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + mdr.getClassMetadata().getClassName(); + ComponentScan cs = AffinityApiTestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringComponentScanUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + + } +} diff --git a/server/test/org/apache/cloudstack/affinity/AffinityApiUnitTest.java b/server/test/org/apache/cloudstack/affinity/AffinityApiUnitTest.java new file mode 100644 index 00000000000..86cf0174579 --- /dev/null +++ b/server/test/org/apache/cloudstack/affinity/AffinityApiUnitTest.java @@ -0,0 +1,174 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.affinity; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.cloud.event.EventUtils; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceInUseException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountManagerImpl; +import com.cloud.user.AccountVO; +import com.cloud.user.UserContext; +import com.cloud.user.UserContextInitializer; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.UserVmDao; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:/affinityContext.xml") +public class AffinityApiUnitTest { + + @Inject + AffinityGroupServiceImpl _affinityService; + + @Inject + AccountManager _acctMgr; + + @Inject + AffinityGroupProcessor _processor; + + @Inject + AffinityGroupDao _groupDao; + + @Inject + UserVmDao _vmDao; + + @Inject + AffinityGroupVMMapDao _affinityGroupVMMapDao; + + @Inject + EventUtils _eventUtils; + + @Inject + AccountDao _accountDao; + + private static long domainId = 5L; + + + @BeforeClass + public static void setUp() throws ConfigurationException { + + } + + @Before + public void testSetUp() { + ComponentContext.initComponentsLifeCycle(); + AccountVO acct = new AccountVO(200L); + acct.setType(Account.ACCOUNT_TYPE_NORMAL); + acct.setAccountName("user"); + acct.setDomainId(domainId); + + UserContext.registerContext(1, acct, null, true); + + when(_acctMgr.finalizeOwner((Account) anyObject(), anyString(), anyLong(), anyLong())).thenReturn(acct); + when(_processor.getType()).thenReturn("mock"); + when(_accountDao.findByIdIncludingRemoved(0L)).thenReturn(acct); + } + + @Test + public void createAffinityGroupTest() { + AffinityGroup group = _affinityService.createAffinityGroup("user", domainId, "group1", "mock", + "affinity group one"); + assertNotNull("Affinity group 'group1' of type 'mock' failed to create ", group); + + } + + @Test(expected = InvalidParameterValueException.class) + public void invalidAffinityTypeTest() { + AffinityGroup group = _affinityService.createAffinityGroup("user", domainId, "group1", "invalid", + "affinity group one"); + + } + + @Test(expected = InvalidParameterValueException.class) + public void uniqueAffinityNameTest() { + when(_groupDao.isNameInUse(anyLong(), anyLong(), eq("group1"))).thenReturn(true); + AffinityGroup group2 = _affinityService.createAffinityGroup("user", domainId, "group1", "mock", + "affinity group two"); + } + + @Test(expected = InvalidParameterValueException.class) + public void deleteAffinityGroupInvalidIdTest() throws ResourceInUseException { + when(_groupDao.findById(20L)).thenReturn(null); + _affinityService.deleteAffinityGroup(20L, "user", domainId, "group1"); + } + + @Test(expected = InvalidParameterValueException.class) + public void deleteAffinityGroupInvalidIdName() throws ResourceInUseException { + when(_groupDao.findByAccountAndName(200L, "group1")).thenReturn(null); + _affinityService.deleteAffinityGroup(null, "user", domainId, "group1"); + } + + @Test(expected = InvalidParameterValueException.class) + public void deleteAffinityGroupNullIdName() throws ResourceInUseException { + _affinityService.deleteAffinityGroup(null, "user", domainId, null); + } + + @Test(expected = ResourceInUseException.class) + public void deleteAffinityGroupInUse() throws ResourceInUseException { + List affinityGroupVmMap = new ArrayList(); + AffinityGroupVMMapVO mapVO = new AffinityGroupVMMapVO(20L, 10L); + affinityGroupVmMap.add(mapVO); + when(_affinityGroupVMMapDao.listByAffinityGroup(20L)).thenReturn(affinityGroupVmMap); + + AffinityGroupVO groupVO = new AffinityGroupVO(); + when(_groupDao.findById(20L)).thenReturn(groupVO); + when(_groupDao.lockRow(20L, true)).thenReturn(groupVO); + + _affinityService.deleteAffinityGroup(20L, "user", domainId, null); + } + + @Test(expected = InvalidParameterValueException.class) + public void updateAffinityGroupVMRunning() throws ResourceInUseException { + + UserVmVO vm = new UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, domainId, 200L, + 5L, "", "test", 1L); + vm.setState(VirtualMachine.State.Running); + when(_vmDao.findById(10L)).thenReturn(vm); + + List affinityGroupIds = new ArrayList(); + affinityGroupIds.add(20L); + + _affinityService.updateVMAffinityGroups(10L, affinityGroupIds); + } + +} diff --git a/server/test/resources/affinityContext.xml b/server/test/resources/affinityContext.xml new file mode 100644 index 00000000000..15476c1a1b3 --- /dev/null +++ b/server/test/resources/affinityContext.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index d99f4c7762a..4a4d3a0042d 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -142,9 +142,32 @@ CREATE TABLE `cloud`.`user_vm_clone_setting` ( INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'UserVmManager', 'vmware.create.full.clone' , 'false', 'If set to true, creates VMs as full clones on ESX hypervisor'); --- Re-enable foreign key checking, at the end of the upgrade path -SET foreign_key_checks = 1; +CREATE TABLE `cloud`.`affinity_group` ( + `id` bigint unsigned NOT NULL auto_increment, + `name` varchar(255) NOT NULL, + `type` varchar(255) NOT NULL, + `uuid` varchar(40), + `description` varchar(4096) NULL, + `domain_id` bigint unsigned NOT NULL, + `account_id` bigint unsigned NOT NULL, + UNIQUE (`name`, `account_id`), + PRIMARY KEY (`id`), + CONSTRAINT `fk_affinity_group__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`), + CONSTRAINT `fk_affinity_group__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`), + CONSTRAINT `uc_affinity_group__uuid` UNIQUE (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `cloud`.`affinity_group_vm_map` ( + `id` bigint unsigned NOT NULL auto_increment, + `affinity_group_id` bigint unsigned NOT NULL, + `instance_id` bigint unsigned NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_agvm__group_id` FOREIGN KEY(`affinity_group_id`) REFERENCES `affinity_group`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_affinity_group_vm_map___instance_id` FOREIGN KEY(`instance_id`) REFERENCES `user_vm` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + +ALTER TABLE `cloud`.`service_offering` ADD COLUMN `deployment_planner` varchar(255) NOT NULL DEFAULT 'FirstFitPlanner' COMMENT 'Planner heuristics used to deploy a VM of this offering'; CREATE TABLE nic_secondary_ips ( `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, @@ -455,3 +478,216 @@ ALTER TABLE `cloud`.`load_balancing_rules` ADD COLUMN `scheme` varchar(40) NOT N UPDATE `cloud`.`load_balancing_rules` SET `scheme`='Public'; + +DROP VIEW IF EXISTS `cloud`.`user_vm_view`; +CREATE VIEW `cloud`.`user_vm_view` AS + select + vm_instance.id id, + vm_instance.name name, + user_vm.display_name display_name, + user_vm.user_data user_data, + account.id account_id, + account.uuid account_uuid, + account.account_name account_name, + account.type account_type, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path, + projects.id project_id, + projects.uuid project_uuid, + projects.name project_name, + instance_group.id instance_group_id, + instance_group.uuid instance_group_uuid, + instance_group.name instance_group_name, + vm_instance.uuid uuid, + vm_instance.last_host_id last_host_id, + vm_instance.vm_type type, + vm_instance.vnc_password vnc_password, + vm_instance.limit_cpu_use limit_cpu_use, + vm_instance.created created, + vm_instance.state state, + vm_instance.removed removed, + vm_instance.ha_enabled ha_enabled, + vm_instance.hypervisor_type hypervisor_type, + vm_instance.instance_name instance_name, + vm_instance.guest_os_id guest_os_id, + guest_os.uuid guest_os_uuid, + vm_instance.pod_id pod_id, + host_pod_ref.uuid pod_uuid, + vm_instance.private_ip_address private_ip_address, + vm_instance.private_mac_address private_mac_address, + vm_instance.vm_type vm_type, + data_center.id data_center_id, + data_center.uuid data_center_uuid, + data_center.name data_center_name, + data_center.is_security_group_enabled security_group_enabled, + host.id host_id, + host.uuid host_uuid, + host.name host_name, + vm_template.id template_id, + vm_template.uuid template_uuid, + vm_template.name template_name, + vm_template.display_text template_display_text, + vm_template.enable_password password_enabled, + iso.id iso_id, + iso.uuid iso_uuid, + iso.name iso_name, + iso.display_text iso_display_text, + service_offering.id service_offering_id, + disk_offering.uuid service_offering_uuid, + service_offering.cpu cpu, + service_offering.speed speed, + service_offering.ram_size ram_size, + disk_offering.name service_offering_name, + storage_pool.id pool_id, + storage_pool.uuid pool_uuid, + storage_pool.pool_type pool_type, + volumes.id volume_id, + volumes.uuid volume_uuid, + volumes.device_id volume_device_id, + volumes.volume_type volume_type, + security_group.id security_group_id, + security_group.uuid security_group_uuid, + security_group.name security_group_name, + security_group.description security_group_description, + nics.id nic_id, + nics.uuid nic_uuid, + nics.network_id network_id, + nics.ip4_address ip_address, + nics.ip6_address ip6_address, + nics.ip6_gateway ip6_gateway, + nics.ip6_cidr ip6_cidr, + nics.default_nic is_default_nic, + nics.gateway gateway, + nics.netmask netmask, + nics.mac_address mac_address, + nics.broadcast_uri broadcast_uri, + nics.isolation_uri isolation_uri, + vpc.id vpc_id, + vpc.uuid vpc_uuid, + networks.uuid network_uuid, + networks.name network_name, + networks.traffic_type traffic_type, + networks.guest_type guest_type, + user_ip_address.id public_ip_id, + user_ip_address.uuid public_ip_uuid, + user_ip_address.public_ip_address public_ip_address, + ssh_keypairs.keypair_name keypair_name, + resource_tags.id tag_id, + resource_tags.uuid tag_uuid, + resource_tags.key tag_key, + resource_tags.value tag_value, + resource_tags.domain_id tag_domain_id, + resource_tags.account_id tag_account_id, + resource_tags.resource_id tag_resource_id, + resource_tags.resource_uuid tag_resource_uuid, + resource_tags.resource_type tag_resource_type, + resource_tags.customer tag_customer, + async_job.id job_id, + async_job.uuid job_uuid, + async_job.job_status job_status, + async_job.account_id job_account_id, + affinity_group.id affinity_group_id, + affinity_group.uuid affinity_group_uuid, + affinity_group.name affinity_group_name, + affinity_group.description affinity_group_description + from + `cloud`.`user_vm` + inner join + `cloud`.`vm_instance` ON vm_instance.id = user_vm.id + and vm_instance.removed is NULL + inner join + `cloud`.`account` ON vm_instance.account_id = account.id + inner join + `cloud`.`domain` ON vm_instance.domain_id = domain.id + left join + `cloud`.`guest_os` ON vm_instance.guest_os_id = guest_os.id + left join + `cloud`.`host_pod_ref` ON vm_instance.pod_id = host_pod_ref.id + left join + `cloud`.`projects` ON projects.project_account_id = account.id + left join + `cloud`.`instance_group_vm_map` ON vm_instance.id = instance_group_vm_map.instance_id + left join + `cloud`.`instance_group` ON instance_group_vm_map.group_id = instance_group.id + left join + `cloud`.`data_center` ON vm_instance.data_center_id = data_center.id + left join + `cloud`.`host` ON vm_instance.host_id = host.id + left join + `cloud`.`vm_template` ON vm_instance.vm_template_id = vm_template.id + left join + `cloud`.`vm_template` iso ON iso.id = user_vm.iso_id + left join + `cloud`.`service_offering` ON vm_instance.service_offering_id = service_offering.id + left join + `cloud`.`disk_offering` ON vm_instance.service_offering_id = disk_offering.id + left join + `cloud`.`volumes` ON vm_instance.id = volumes.instance_id + left join + `cloud`.`storage_pool` ON volumes.pool_id = storage_pool.id + left join + `cloud`.`security_group_vm_map` ON vm_instance.id = security_group_vm_map.instance_id + left join + `cloud`.`security_group` ON security_group_vm_map.security_group_id = security_group.id + left join + `cloud`.`nics` ON vm_instance.id = nics.instance_id + left join + `cloud`.`networks` ON nics.network_id = networks.id + left join + `cloud`.`vpc` ON networks.vpc_id = vpc.id + left join + `cloud`.`user_ip_address` ON user_ip_address.vm_id = vm_instance.id + left join + `cloud`.`user_vm_details` ON user_vm_details.vm_id = vm_instance.id + and user_vm_details.name = 'SSH.PublicKey' + left join + `cloud`.`ssh_keypairs` ON ssh_keypairs.public_key = user_vm_details.value + left join + `cloud`.`resource_tags` ON resource_tags.resource_id = vm_instance.id + and resource_tags.resource_type = 'UserVm' + left join + `cloud`.`async_job` ON async_job.instance_id = vm_instance.id + and async_job.instance_type = 'VirtualMachine' + and async_job.job_status = 0 + left join + `cloud`.`affinity_group_vm_map` ON vm_instance.id = affinity_group_vm_map.instance_id + left join + `cloud`.`affinity_group` ON affinity_group_vm_map.affinity_group_id = affinity_group.id; + +DROP VIEW IF EXISTS `cloud`.`affinity_group_view`; +CREATE VIEW `cloud`.`affinity_group_view` AS + select + affinity_group.id id, + affinity_group.name name, + affinity_group.description description, + affinity_group.uuid uuid, + account.id account_id, + account.uuid account_uuid, + account.account_name account_name, + account.type account_type, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path, + vm_instance.id vm_id, + vm_instance.uuid vm_uuid, + vm_instance.name vm_name, + vm_instance.state vm_state, + user_vm.display_name vm_display_name + from + `cloud`.`affinity_group` + inner join + `cloud`.`account` ON affinity_group.account_id = account.id + inner join + `cloud`.`domain` ON affinity_group.domain_id = domain.id + left join + `cloud`.`affinity_group_vm_map` ON affinity_group.id = affinity_group_vm_map.affinity_group_id + left join + `cloud`.`vm_instance` ON vm_instance.id = affinity_group_vm_map.instance_id + left join + `cloud`.`user_vm` ON user_vm.id = vm_instance.id; + +-- Re-enable foreign key checking, at the end of the upgrade path +SET foreign_key_checks = 1; diff --git a/test/integration/smoke/test_affinity_groups.py b/test/integration/smoke/test_affinity_groups.py new file mode 100644 index 00000000000..832c4a4e1fa --- /dev/null +++ b/test/integration/smoke/test_affinity_groups.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from marvin import remoteSSHClient +from nose.plugins.attrib import attr + +class Services: + """Test Account Services + """ + + def __init__(self): + self.services = { + "domain": { + "name": "Domain", + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + # in MHz + "memory": 64, + # In MBs + }, + "ostype": 'CentOS 5.3 (64-bit)', + "virtual_machine" : { + "affinity": { + "name": "webvms", + "type": "host anti-affinity", + }, + "hypervisor" : "XenServer", + } + } + + +class TestDeployVmWithAffinityGroup(cloudstackTestCase): + """ + This test deploys a virtual machine into a user account + using the small service offering and builtin template + """ + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestDeployVmWithAffinityGroup, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + cls.template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + cls.services["template"] = cls.template.id + cls.services["zoneid"] = cls.zone.id + + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + + cls.ag = AffinityGroup.create(cls.api_client, cls.services["virtual_machine"]["affinity"], + account=cls.services["account"], domainid=cls.domain.id) + + cls._cleanup = [ + cls.service_offering, + cls.account, + ] + return + + @attr(tags=["simulator", "basic", "advanced", "multihost"]) + def test_DeployVmAntiAffinityGroup(self): + """ + test DeployVM in anti-affinity groups + + deploy VM1 and VM2 in the same host-anti-affinity groups + Verify that the vms are deployed on separate hosts + """ + #deploy VM1 in affinity group created in setUp + vm1 = VirtualMachine.create( + self.api_client, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.account.name, + domainid=self.account.account.domainid, + serviceofferingid=self.service_offering.id, + affinitygroupnames=[self.ag.name] + ) + + list_vm1 = list_virtual_machines( + self.api_client, + id=vm1.id + ) + self.assertEqual( + isinstance(list_vm1, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vm1), + 0, + "Check VM available in List Virtual Machines" + ) + vm1_response = list_vm1[0] + self.assertEqual( + vm1_response.state, + 'Running', + msg="VM is not in Running state" + ) + host_of_vm1 = vm1_response.hostid + + #deploy VM2 in affinity group created in setUp + vm2 = VirtualMachine.create( + self.api_client, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.account.name, + domainid=self.account.account.domainid, + serviceofferingid=self.service_offering.id, + affinitygroupnames=[self.ag.name] + ) + list_vm2 = list_virtual_machines( + self.api_client, + id=vm2.id + ) + self.assertEqual( + isinstance(list_vm2, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vm2), + 0, + "Check VM available in List Virtual Machines" + ) + vm2_response = list_vm2[0] + self.assertEqual( + vm2_response.state, + 'Running', + msg="VM is not in Running state" + ) + host_of_vm2 = vm2_response.hostid + + self.assertNotEqual(host_of_vm1, host_of_vm2, + msg="Both VMs of affinity group %s are on the same host" % self.ag.name) + + + @classmethod + def tearDown(cls): + try: + cls.api_client = super(TestDeployVmWithAffinityGroup, cls).getClsTestClient().getApiClient() + #Clean up, terminate the created templates + cleanup_resources(cls.api_client, cls.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index ab2456dd3eb..2d5292b8426 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/cygdrive/c/Python27 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -137,6 +137,7 @@ known_categories = { 'addIpToNic': 'Nic', 'removeIpFromNic': 'Nic', 'listNics':'Nic', + 'AffinityGroup': 'Affinity Group', } diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index f3370eb3190..d27ab3b2903 100644 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -90,10 +90,7 @@ class Account: cmd.firstname = services["firstname"] cmd.lastname = services["lastname"] - # Password Encoding - mdf = hashlib.md5() - mdf.update(services["password"]) - cmd.password = mdf.hexdigest() + cmd.password = services["password"] cmd.username = "-".join([services["username"], random_gen()]) if domainid: @@ -223,7 +220,7 @@ class VirtualMachine: def create(cls, apiclient, services, templateid=None, accountid=None, domainid=None, zoneid=None, networkids=None, serviceofferingid=None, securitygroupids=None, projectid=None, startvm=None, - diskofferingid=None, hostid=None, mode='basic'): + diskofferingid=None, affinitygroupnames=None, hostid=None, mode='basic'): """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() @@ -268,6 +265,11 @@ class VirtualMachine: if "userdata" in services: cmd.userdata = base64.b64encode(services["userdata"]) + if "affinitygroupnames" in services: + cmd.affinitygroupnames = services["affinitygroupnames"] + elif affinitygroupnames: + cmd.affinitygroupnames = affinitygroupnames + if projectid: cmd.projectid = projectid @@ -2424,3 +2426,33 @@ class VPC: cmd = listVPCs.listVPCsCmd() [setattr(cmd, k, v) for k, v in kwargs.items()] return(apiclient.listVPCs(cmd)) + + +class AffinityGroup: + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, account=None, domainid=None): + agCmd = createAffinityGroup.createAffinityGroupCmd() + agCmd.name = services['name'] + agCmd.displayText = services['displaytext'] if 'displaytext' in services else services['name'] + agCmd.type = services['type'] + agCmd.account = services['account'] if 'account' in services else account + agCmd.domainid = services['domainid'] if 'domainid' in services else domainid + return AffinityGroup(apiclient.createAffinityGroup(agCmd).__dict__) + + def update(self, apiclient): + pass + + def delete(self, apiclient): + cmd = deleteAffinityGroup.deleteAffinityGroupCmd() + cmd.id = self.id + return apiclient.deleteVPC(cmd) + + + @classmethod + def list(cls, apiclient, **kwargs): + cmd = listAffinityGroups.listAffinityGroupsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVPCs(cmd)) diff --git a/ui/index.jsp b/ui/index.jsp index 3b8f37886ef..46f49f00984 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -56,7 +56,7 @@ under the License.
- +
diff --git a/ui/scripts/cloudStack.js b/ui/scripts/cloudStack.js index 0cf80b02066..a8bca91769c 100644 --- a/ui/scripts/cloudStack.js +++ b/ui/scripts/cloudStack.js @@ -255,11 +255,11 @@ array1.push("&domain=" + encodeURIComponent("/")); } - g_loginCmdText = array1.join(""); + var loginCmdText = array1.join(""); $.ajax({ type: "POST", - data: "command=login" + g_loginCmdText + "&response=json", + data: "command=login" + loginCmdText + "&response=json", dataType: "json", async: false, success: function(json) { diff --git a/ui/scripts/network.js b/ui/scripts/network.js index 6c311923faf..b6ed0fe9904 100755 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -1554,7 +1554,9 @@ data.queryasyncjobresultresponse.jobresult.nicsecondaryip, { zoneid: args.context.instances[0].zoneid, - virtualmachinedisplayname: args.context.instances[0].displayname + virtualmachinedisplayname: args.context.instances[0].displayname ? + args.context.instances[0].displayname : + args.context.instances[0].name } ); }, @@ -1587,7 +1589,9 @@ data: $(ips).map(function(index, ip) { return $.extend(ip, { zoneid: args.context.instances[0].zoneid, - virtualmachinedisplayname: args.context.instances[0].displayname + virtualmachinedisplayname: args.context.instances[0].displayname ? + args.context.instances[0].displayname : + args.context.instances[0].name }); }) }); diff --git a/ui/scripts/regions.js b/ui/scripts/regions.js index 0aaece009f7..10057a62a4a 100644 --- a/ui/scripts/regions.js +++ b/ui/scripts/regions.js @@ -214,8 +214,148 @@ id: 'GSLB', label: 'GSLB', fields: { - name: { label: 'label.name' } + name: { label: 'label.name' }, + gslbdomainname: { label: 'GSLB Domain Name' }, + gslblbmethod: { label: 'Algorithm' } + }, + actions: { + add: { + label: 'Add GSLB', + + messages: { + confirm: function(args) { + return 'Add GSLB'; + }, + notification: function(args) { + return 'Add GSLB'; + } + }, + + createForm: { + title: 'Add GSLB', + fields: { + name: { + label: 'label.name', + validation: { required: true } + }, + description: { + label: 'label.description' + }, + /* + domainid: { + label: 'Domain', + select: function(args) { + if(isAdmin() || isDomainAdmin()) { + $.ajax({ + url: createURL('listDomains'), + data: { + listAll: true, + details: 'min' + }, + success: function(json) { + var array1 = [{id: '', description: ''}]; + var domains = json.listdomainsresponse.domain; + if(domains != null && domains.length > 0) { + for(var i = 0; i < domains.length; i++) { + array1.push({id: domains[i].id, description: domains[i].path}); + } + } + args.response.success({ + data: array1 + }); + } + }); + } + else { + args.response.success({ + data: null + }); + } + }, + isHidden: function(args) { + if(isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + account: { + label: 'Account', + isHidden: function(args) { + if(isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + */ + gslblbmethod: { + label: 'Algorithm', + select: function(args) { + var array1 = [{id: 'roundrobin', description: 'roundrobin'}, {id: 'leastconn', description: 'leastconn'}, {id: 'proximity', description: 'proximity'}]; + args.response.success({ + data: array1 + }); + } + }, + gslbdomainname: { + label: 'GSLB Domain Name', + validation: { required: true } + }, + gslbservicetype: { + label: 'Service Type', + select: function(args) { + var array1 = [{id: 'tcp', description: 'tcp'}, {id: 'udp', description: 'udp'}]; + args.response.success({ + data: array1 + }); + }, + validation: { required: true } + } + } + }, + action: function(args) { + var data = { + name: args.data.name, + regionid: args.context.regions[0].id, + gslblbmethod: args.data.gslblbmethod, + gslbstickysessionmethodname: 'sourceip', + gslbdomainname: args.data.gslbdomainname, + gslbservicetype: args.data.gslbservicetype + }; + if(args.data.description != null && args.data.description.length > 0) + $.extend(data, { description: args.data.description }); + /* + if(args.data.domainid != null && args.data.domainid.length > 0) + $.extend(data, { domainid: args.data.domainid }); + if(args.data.account != null && args.data.account.length > 0) + $.extend(data, { account: args.data.account }); + */ + $.ajax({ + url: createURL('createGlobalLoadBalancerRule'), + data: data, + success: function(json) { + var jid = json.creategloballoadbalancerruleresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.globalloadbalancerrule; + } + } + } + ); + } + }); + }, + notification: { + poll: function(args) { + poll: pollAsyncJobResult + } + } + } }, + dataProvider: function(args) { if('regions' in args.context) { var data = { @@ -224,8 +364,7 @@ $.ajax({ url: createURL('listGlobalLoadBalancerRules'), data: data, - success: function(json) { - debugger; + success: function(json) { var items = json.listgloballoadbalancerrulesresponse.globalloadbalancerrule; args.response.success({ data: items diff --git a/ui/scripts/ui-custom/regions.js b/ui/scripts/ui-custom/regions.js index 2f61dfddeaf..0469638f8f2 100644 --- a/ui/scripts/ui-custom/regions.js +++ b/ui/scripts/ui-custom/regions.js @@ -89,7 +89,7 @@ closeRegionSelector({ complete: function() { $('#container').prepend($('
').addClass('loading-overlay')); - window.name = g_loginCmdText; + //window.name = g_loginCmdText; document.location.href = url; } }); diff --git a/ui/scripts/ui/widgets/listView.js b/ui/scripts/ui/widgets/listView.js index 1c74056c75b..c5e0561606f 100644 --- a/ui/scripts/ui/widgets/listView.js +++ b/ui/scripts/ui/widgets/listView.js @@ -863,16 +863,14 @@ var uiCustom = listViewArgs.uiCustom; var subselect = uiCustom ? listViewArgs.listView.subselect : null; - if (!data || !data.length) { + if (!(data && data.length)) { if (!$tbody.find('tr').size()) { return [ - $('').addClass('empty').append( + $('').addClass('empty last').append( $('').html(_l('label.no.data')) ).appendTo($tbody) ]; } - - return $tbody.find('tr:last').addClass('last'); } $tbody.find('tr.empty').remove(); diff --git a/utils/src/com/cloud/utils/SerialVersionUID.java b/utils/src/com/cloud/utils/SerialVersionUID.java index 35e9446b17e..856d5639c50 100755 --- a/utils/src/com/cloud/utils/SerialVersionUID.java +++ b/utils/src/com/cloud/utils/SerialVersionUID.java @@ -61,4 +61,5 @@ public interface SerialVersionUID { public static final long CloudExecutionException = Base | 0x27; public static final long CallFailedException = Base | 0x28; public static final long UnableDeleteHostException = Base | 0x29; + public static final long AffinityConflictException = Base | 0x2a; }