diff --git a/api/src/com/cloud/agent/api/to/GPUDeviceTO.java b/api/src/com/cloud/agent/api/to/GPUDeviceTO.java new file mode 100644 index 00000000000..8bc9e5229de --- /dev/null +++ b/api/src/com/cloud/agent/api/to/GPUDeviceTO.java @@ -0,0 +1,56 @@ +// 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.agent.api.to; + +import java.util.HashMap; + +public class GPUDeviceTO { + + private String gpuGroup; + private String vgpuType; + private HashMap> groupDetails = new HashMap>(); + + public GPUDeviceTO( String gpuGroup, String vgpuType, HashMap> groupDetails) { + this.gpuGroup = gpuGroup; + this.vgpuType = vgpuType; + this.groupDetails = groupDetails; + } + + public String getGpuGroup() { + return gpuGroup; + } + + public String getVgpuType() { + return vgpuType; + } + + public void setGpuGroup(String gpuGroup) { + this.gpuGroup = gpuGroup; + } + + public void setVgpuType(String vgpuType) { + this.vgpuType = vgpuType; + } + + public HashMap> getGroupDetails() { + return groupDetails; + } + + public void setGroupDetails(HashMap> groupDetails) { + this.groupDetails = groupDetails; + } +} diff --git a/api/src/com/cloud/agent/api/to/VirtualMachineTO.java b/api/src/com/cloud/agent/api/to/VirtualMachineTO.java index bed3e1d6aaf..bbd83852abf 100644 --- a/api/src/com/cloud/agent/api/to/VirtualMachineTO.java +++ b/api/src/com/cloud/agent/api/to/VirtualMachineTO.java @@ -60,6 +60,7 @@ public class VirtualMachineTO { DiskTO[] disks; NicTO[] nics; + GPUDeviceTO gpuDevice; public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType bootloader, String os, boolean enableHA, boolean limitCpuUse, String vncPassword) { @@ -266,4 +267,12 @@ public class VirtualMachineTO { this.uuid = uuid; } + public GPUDeviceTO getGpuDevice() { + return gpuDevice; + } + + public void setGpuDevice(GPUDeviceTO gpuDevice) { + this.gpuDevice = gpuDevice; + } + } diff --git a/api/src/com/cloud/gpu/GPU.java b/api/src/com/cloud/gpu/GPU.java new file mode 100644 index 00000000000..0eb466758fc --- /dev/null +++ b/api/src/com/cloud/gpu/GPU.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 com.cloud.gpu; + + +public class GPU { + + public enum Keys { + pciDevice, + vgpuType + } + public enum Type { + GPU_Passthrough, + VGPU + } + + public enum vGPUType { + GRID_K100("GRID K100"), + GRID_K140Q("GRID K140Q"), + GRID_K200("GRID K200"), + GRID_K240Q("GRID K240Q"), + GRID_K260("GRID K260Q"), + passthrough("passthrough"); + + private String type; + + vGPUType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + } +} diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index f0de48e0c0e..0a3fafda312 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -581,6 +581,11 @@ public class ApiConstants { public static final String PASSIVE = "passive"; public static final String VERSION = "version"; public static final String START = "start"; + public static final String GPUGROUP = "gpugroup"; + public static final String GPUGROUPNAME = "gpugroupname"; + public static final String VGPU = "vgpu"; + public static final String VGPUTYPE = "vgputype"; + public static final String REMAININGCAPACITY = "remainingcapacity"; public enum HostDetails { all, capacity, events, stats, min; 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 4143c2e44c7..42bd95dd7ce 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 @@ -17,6 +17,8 @@ package org.apache.cloudstack.api.command.admin.offering; import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import org.apache.log4j.Logger; @@ -206,13 +208,17 @@ public class CreateServiceOfferingCmd extends BaseCmd { } public Map getDetails() { - if (details == null || details.isEmpty()) { - return null; + Map detailsMap = null; + if (details != null && !details.isEmpty()) { + detailsMap = new HashMap(); + Collection props = details.values(); + Iterator iter = props.iterator(); + while (iter.hasNext()) { + HashMap detail = (HashMap) iter.next(); + detailsMap.putAll(detail); + } } - - Collection paramsCollection = details.values(); - Map params = (Map)(paramsCollection.toArray())[0]; - return params; + return detailsMap; } public Long getBytesReadRate() { diff --git a/api/src/org/apache/cloudstack/api/response/GpuResponse.java b/api/src/org/apache/cloudstack/api/response/GpuResponse.java new file mode 100644 index 00000000000..b655749126e --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/GpuResponse.java @@ -0,0 +1,46 @@ +// 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 java.util.List; + +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; + +public class GpuResponse extends BaseResponse { + + @SerializedName(ApiConstants.GPUGROUPNAME) + @Param(description = "GPU cards present in the host") + private String gpuGroupName; + + @SerializedName(ApiConstants.VGPU) + @Param(description = "the list of enabled vGPUs", responseObject = VgpuResponse.class) + private List vgpu; + + public void setGpuGroupName(String gpuGroupName) { + this.gpuGroupName = gpuGroupName; + } + + public void setVgpu(List vgpu) { + this.vgpu = vgpu; + } + +} diff --git a/api/src/org/apache/cloudstack/api/response/HostResponse.java b/api/src/org/apache/cloudstack/api/response/HostResponse.java index e2d8eb5c473..1fbc6684e13 100644 --- a/api/src/org/apache/cloudstack/api/response/HostResponse.java +++ b/api/src/org/apache/cloudstack/api/response/HostResponse.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.response; import java.util.Date; +import java.util.List; import com.google.gson.annotations.SerializedName; @@ -135,6 +136,10 @@ public class HostResponse extends BaseResponse { @Param(description = "the amount of the host's memory currently used") private Long memoryUsed; + @SerializedName(ApiConstants.GPUGROUP) + @Param(description = "GPU cards present in the host", responseObject = GpuResponse.class) + private List gpuGroup; + @SerializedName("disksizetotal") @Param(description = "the total disk size of the host") private Long diskSizeTotal; @@ -320,6 +325,9 @@ public class HostResponse extends BaseResponse { this.memoryUsed = memoryUsed; } + public void setGpuGroups(List gpuGroup) { + this.gpuGroup = gpuGroup; + } public void setDiskSizeTotal(Long diskSizeTotal) { this.diskSizeTotal = diskSizeTotal; } diff --git a/api/src/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/org/apache/cloudstack/api/response/UserVmResponse.java index 84d532bbc17..d6ce84f4951 100644 --- a/api/src/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/org/apache/cloudstack/api/response/UserVmResponse.java @@ -164,6 +164,10 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp @Param(description = "the memory allocated for the virtual machine") private Integer memory; + @SerializedName(ApiConstants.VGPU) + @Param(description = "the vgpu type used by the virtual machine") + private String vgpu; + @SerializedName("cpuused") @Param(description = "the amount of the vm's CPU currently used") private String cpuUsed; @@ -420,6 +424,9 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp return memory; } + public String getVgpu() { + return vgpu; + } public String getCpuUsed() { return cpuUsed; } @@ -643,6 +650,9 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp this.memory = memory; } + public void setVgpu(String vgpu) { + this.vgpu = vgpu; + } public void setCpuUsed(String cpuUsed) { this.cpuUsed = cpuUsed; } diff --git a/api/src/org/apache/cloudstack/api/response/VgpuResponse.java b/api/src/org/apache/cloudstack/api/response/VgpuResponse.java new file mode 100644 index 00000000000..17e194be5b1 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/VgpuResponse.java @@ -0,0 +1,52 @@ +// 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 com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; + +public class VgpuResponse extends BaseResponse { + + @SerializedName(ApiConstants.VGPUTYPE) + @Param(description = "Model Name of vGPU") + private String name; + + @SerializedName(ApiConstants.REMAININGCAPACITY) + @Param(description = "No. of more VMs can be deployped with this vGPU type") + private Long capacity; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getCapacity() { + return capacity; + } + + public void setCapacity(Long capacity) { + this.capacity = capacity; + } + +} diff --git a/core/src/com/cloud/agent/api/GetGPUStatsAnswer.java b/core/src/com/cloud/agent/api/GetGPUStatsAnswer.java new file mode 100644 index 00000000000..566eed5ab63 --- /dev/null +++ b/core/src/com/cloud/agent/api/GetGPUStatsAnswer.java @@ -0,0 +1,36 @@ +//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.agent.api; + +import java.util.HashMap; + +import com.cloud.agent.api.LogLevel.Log4jLevel; + +@LogLevel(Log4jLevel.Trace) +public class GetGPUStatsAnswer extends Answer { + + private HashMap> groupDetails; + + public GetGPUStatsAnswer(GetGPUStatsCommand cmd, HashMap> groupDetails) { + super(cmd); + this.groupDetails = groupDetails; + } + + public HashMap> getGroupDetails() { + return this.groupDetails; + } +} diff --git a/core/src/com/cloud/agent/api/GetGPUStatsCommand.java b/core/src/com/cloud/agent/api/GetGPUStatsCommand.java new file mode 100644 index 00000000000..047f56283e0 --- /dev/null +++ b/core/src/com/cloud/agent/api/GetGPUStatsCommand.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 com.cloud.agent.api; + +import com.cloud.agent.api.LogLevel.Log4jLevel; + +@LogLevel(Log4jLevel.Trace) +public class GetGPUStatsCommand extends Command +{ + String hostGuid; + String hostName; + + protected GetGPUStatsCommand() { + } + + public GetGPUStatsCommand(String hostGuid, String hostName) { + this.hostGuid = hostGuid; + this.hostName = hostName; + } + + public String getHostGuid(){ + return this.hostGuid; + } + + public String getHostName(){ + return this.hostName; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/com/cloud/agent/api/StartupRoutingCommand.java b/core/src/com/cloud/agent/api/StartupRoutingCommand.java index 8c0eb8a4ecd..dc371133861 100755 --- a/core/src/com/cloud/agent/api/StartupRoutingCommand.java +++ b/core/src/com/cloud/agent/api/StartupRoutingCommand.java @@ -70,6 +70,7 @@ public class StartupRoutingCommand extends StartupCommand { HypervisorType hypervisorType; Map hostDetails; //stuff like host os, cpu capabilities String hypervisorVersion; + HashMap> groupDetails = new HashMap>(); public StartupRoutingCommand() { super(Host.Type.Routing); @@ -244,4 +245,12 @@ public class StartupRoutingCommand extends StartupCommand { public void setHostVmStateReport(Map hostVmStateReport) { this._hostVmStateReport = hostVmStateReport; } + + public HashMap> getGpuGroupDetails() { + return groupDetails; + } + + public void setGpuGroupDetails(HashMap> groupDetails) { + this.groupDetails = groupDetails; + } } diff --git a/core/src/com/cloud/agent/api/StopCommand.java b/core/src/com/cloud/agent/api/StopCommand.java index 6a29aa69ba9..00d7f5fff6c 100755 --- a/core/src/com/cloud/agent/api/StopCommand.java +++ b/core/src/com/cloud/agent/api/StopCommand.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.agent.api; +import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.vm.VirtualMachine; public class StopCommand extends RebootCommand { @@ -23,6 +24,7 @@ public class StopCommand extends RebootCommand { private String urlPort = null; private String publicConsoleProxyIpAddress = null; boolean executeInSequence = false; + private GPUDeviceTO gpuDevice; protected StopCommand() { } @@ -62,4 +64,11 @@ public class StopCommand extends RebootCommand { return this.publicConsoleProxyIpAddress; } + public GPUDeviceTO getGpuDevice() { + return this.gpuDevice; + } + + public void setGpuDevice(GPUDeviceTO gpuDevice) { + this.gpuDevice = gpuDevice; + } } diff --git a/engine/components-api/src/com/cloud/resource/ResourceManager.java b/engine/components-api/src/com/cloud/resource/ResourceManager.java index 95fb3853717..5a9bddb1863 100755 --- a/engine/components-api/src/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/com/cloud/resource/ResourceManager.java @@ -17,15 +17,18 @@ // under the License. package com.cloud.resource; +import java.util.HashMap; import java.util.List; import java.util.Map; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.PodCluster; import com.cloud.exception.AgentUnavailableException; +import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.HostStats; @@ -137,4 +140,42 @@ public interface ResourceManager extends ResourceService { * @return */ List listAllUpAndEnabledNonHAHosts(Type type, Long clusterId, Long podId, long dcId); + + /** + * Check if host has GPU devices available + * @param hostId the host to be checked + * @param vgpuType the VGPU type + * @return true when the host has the capacity with given VGPU type + */ + boolean isGPUDeviceAvailable(long hostId, String vgpuType); + + /** + * Get available GPU device + * @param hostId the host to be checked + * @param vgpuType the VGPU type + * @return GPUDeviceTO[] + */ + GPUDeviceTO getGPUDevice(long hostId, String vgpuType); + + /** + * Return listof available GPU devices + * @param hostId, the host to be checked + * @param vgpuType the VGPU type + * @return List of HostGpuGroupsVO. + */ + List listAvailableGPUDevice(long hostId, String vgpuType); + + /** + * Update GPU device details (post VM deployment) + * @param hostId, the dest host Id + * @param groupDetails, capacity of GPU group. + */ + void updateGPUDetails(long hostId, HashMap> groupDetails); + + /** + * Get GPU details for a host + * @param host, the Host object + * @return Details of groupNames and enabled VGPU type with remaining capacity. + */ + HashMap> getGPUStatistics(HostVO host); } diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java index 8edbb76793c..609aefa4b2a 100755 --- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -103,6 +103,7 @@ import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.manager.Commands; @@ -139,6 +140,7 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; +import com.cloud.gpu.dao.VGPUTypesDao; import com.cloud.ha.HighAvailabilityManager; import com.cloud.ha.HighAvailabilityManager.WorkType; import com.cloud.host.Host; @@ -276,6 +278,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject protected AffinityGroupVMMapDao _affinityGroupVMMapDao; @Inject + protected VGPUTypesDao _vgpuTypesDao; + @Inject protected EntityManager _entityMgr; @Inject @@ -1026,6 +1030,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new ConcurrentOperationException("Unable to transition to a new state."); } + // Update GPU device capacity + GPUDeviceTO gpuDevice = startAnswer.getVirtualMachine().getGpuDevice(); + if (gpuDevice != null) { + _resourceMgr.updateGPUDetails(destHostId, gpuDevice.getGroupDetails()); + } + startedVm = vm; if (s_logger.isDebugEnabled()) { s_logger.debug("Start completed for VM " + vm); @@ -1221,6 +1231,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } + GPUDeviceTO gpuDevice = stop.getGpuDevice(); + if (gpuDevice != null) { + _resourceMgr.updateGPUDetails(vm.getHostId(), gpuDevice.getGroupDetails()); + } if (!answer.getResult()) { s_logger.debug("Unable to stop VM due to " + answer.getDetails()); return false; @@ -1483,6 +1497,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new CloudRuntimeException("Unable to stop the virtual machine due to " + answer.getDetails()); } vmGuru.finalizeStop(profile, answer); + GPUDeviceTO gpuDevice = stop.getGpuDevice(); + if (gpuDevice != null) { + _resourceMgr.updateGPUDetails(vm.getHostId(), gpuDevice.getGroupDetails()); + } } else { throw new CloudRuntimeException("Invalid answer received in response to a StopCommand on " + vm.instanceName); } diff --git a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 08efb8345f5..765d86c34ad 100644 --- a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -316,6 +316,8 @@ + + diff --git a/engine/schema/src/com/cloud/gpu/HostGpuGroupsVO.java b/engine/schema/src/com/cloud/gpu/HostGpuGroupsVO.java new file mode 100644 index 00000000000..1c186a162e3 --- /dev/null +++ b/engine/schema/src/com/cloud/gpu/HostGpuGroupsVO.java @@ -0,0 +1,70 @@ +// 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.gpu; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name="host_gpu_groups") +public class HostGpuGroupsVO implements InternalIdentity { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private long id; + + @Column(name="group_name") + private String groupName; + + @Column(name="host_id") + private long hostId; + + protected HostGpuGroupsVO() { + } + + public HostGpuGroupsVO(long hostId, String groupName) { + this.hostId = hostId; + this.groupName = groupName; + } + + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + @Override + public long getId() { + return id; + } +} diff --git a/engine/schema/src/com/cloud/gpu/VGPUTypesVO.java b/engine/schema/src/com/cloud/gpu/VGPUTypesVO.java new file mode 100644 index 00000000000..586a0c92798 --- /dev/null +++ b/engine/schema/src/com/cloud/gpu/VGPUTypesVO.java @@ -0,0 +1,82 @@ +// 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.gpu; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name="vgpu_types") +public class VGPUTypesVO implements InternalIdentity { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private long id; + + @Column(name="vgpu_type") + private String vgpuType; + + @Column(name="gpu_group_id") + private long gpuGroupId; + + @Column(name="remaining_vm_capacity") + private long remainingCapacity; + + protected VGPUTypesVO() { + } + + public VGPUTypesVO(String vgpuType, long gpuGroupId, long remainingCapacity) { + this.vgpuType = vgpuType; + this.gpuGroupId = gpuGroupId; + this.remainingCapacity = remainingCapacity; + } + + public String getVgpuType() { + return vgpuType; + } + + public void setVgpuType(String vgpuType) { + this.vgpuType = vgpuType; + } + + public long getGpuGroupId() { + return gpuGroupId; + } + + public void setGpuGroupId(long gpuGroupId) { + this.gpuGroupId = gpuGroupId; + } + + public long getRemainingCapacity() { + return remainingCapacity; + } + + public void setRemainingCapacity(long remainingCapacity) { + this.remainingCapacity = remainingCapacity; + } + + @Override + public long getId() { + return id; + } +} diff --git a/engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDao.java b/engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDao.java new file mode 100644 index 00000000000..58641443972 --- /dev/null +++ b/engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDao.java @@ -0,0 +1,60 @@ +// 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.gpu.dao; + +import java.util.List; + +import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.utils.db.GenericDao; + +public interface HostGpuGroupsDao extends GenericDao { + + /** + * Find host device by hostId and PCI ID + * @param hostId the host + * @param groupName GPU group + * @return HostGpuGroupsVO + */ + HostGpuGroupsVO findByHostIdGroupName(long hostId, String groupName); + + /** + * List all the host Ids, that are GPU enabled. + * @return list of hostIds + */ + List listHostIds(); + + /** + * Return a list by hostId. + * @param hostId the host + * @return HostGpuGroupsVO + */ + List listByHostId(long hostId); + + /** + * Delete entries by hostId. + * @param hostId the host + */ + void deleteGpuEntries(long hostId); + + /** + * Save the list of GPU groups belonging to a host + * @param hostId the host + * @param gpuGroups the list of GPU groups to save + */ + void persist(long hostId, List gpuGroups); + +} diff --git a/engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java b/engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java new file mode 100644 index 00000000000..6bddea262ed --- /dev/null +++ b/engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java @@ -0,0 +1,94 @@ +// 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.gpu.dao; + +import java.util.List; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +@Local(value = HostGpuGroupsDao.class) +public class HostGpuGroupsDaoImpl extends GenericDaoBase implements HostGpuGroupsDao { + private static final Logger s_logger = Logger.getLogger(HostGpuGroupsDaoImpl.class); + + private final SearchBuilder _hostIdGroupNameSearch; + private final SearchBuilder _searchByHostId; + private final GenericSearchBuilder _searchHostIds; + + public HostGpuGroupsDaoImpl() { + + _hostIdGroupNameSearch = createSearchBuilder(); + _hostIdGroupNameSearch.and("hostId", _hostIdGroupNameSearch.entity().getHostId(), SearchCriteria.Op.EQ); + _hostIdGroupNameSearch.and("groupName", _hostIdGroupNameSearch.entity().getGroupName(), SearchCriteria.Op.EQ); + _hostIdGroupNameSearch.done(); + + _searchByHostId = createSearchBuilder(); + _searchByHostId.and("hostId", _searchByHostId.entity().getHostId(), SearchCriteria.Op.EQ); + _searchByHostId.done(); + + _searchHostIds = createSearchBuilder(Long.class); + _searchHostIds.selectFields(_searchHostIds.entity().getHostId()); + _searchHostIds.done(); + } + + @Override + public HostGpuGroupsVO findByHostIdGroupName(long hostId, String groupName) { + SearchCriteria sc = _hostIdGroupNameSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("groupName", groupName); + return findOneBy(sc); + } + + @Override + public List listHostIds() { + SearchCriteria sc = _searchHostIds.create(); + return customSearch(sc, null); + } + + @Override + public List listByHostId(long hostId) { + SearchCriteria sc = _searchByHostId.create(); + sc.setParameters("hostId", hostId); + return listBy(sc); + } + + @Override + public void persist(long hostId, List gpuGroups) { + for (String groupName : gpuGroups) { + if (findByHostIdGroupName(hostId, groupName) == null) { + HostGpuGroupsVO record = new HostGpuGroupsVO(hostId, groupName); + persist(record); + } + } + } + + @Override + public void deleteGpuEntries(long hostId) { + SearchCriteria sc = _searchByHostId.create(); + sc.setParameters("hostId", hostId); + remove(sc); + } +} diff --git a/engine/schema/src/com/cloud/gpu/dao/VGPUTypesDao.java b/engine/schema/src/com/cloud/gpu/dao/VGPUTypesDao.java new file mode 100644 index 00000000000..2872cfda886 --- /dev/null +++ b/engine/schema/src/com/cloud/gpu/dao/VGPUTypesDao.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 com.cloud.gpu.dao; + +import java.util.HashMap; +import java.util.List; + +import com.cloud.gpu.VGPUTypesVO; +import com.cloud.utils.db.GenericDao; + +public interface VGPUTypesDao extends GenericDao { + + /** + * Find VGPU types by group Id + * @param groupId of the GPU group + * @return list of VGPUTypesVO + */ + List listByGroupId(long groupId); + + /** + * Find VGPU type by group Id and VGPU type + * @param groupId of the GPU group + * @param vgpuType name of VGPU type + * @return VGPUTypesVO + */ + VGPUTypesVO findByGroupIdVGPUType(long groupId, String vgpuType); + + /** + * Save the list of enabled VGPU types + * @param hostId the host + * @param groupDetails with enabled VGPU types + */ + void persist(long hostId, HashMap> groupDetails); +} diff --git a/engine/schema/src/com/cloud/gpu/dao/VGPUTypesDaoImpl.java b/engine/schema/src/com/cloud/gpu/dao/VGPUTypesDaoImpl.java new file mode 100644 index 00000000000..7cc83cc9eb8 --- /dev/null +++ b/engine/schema/src/com/cloud/gpu/dao/VGPUTypesDaoImpl.java @@ -0,0 +1,95 @@ +//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.gpu.dao; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + +import javax.ejb.Local; +import javax.inject.Inject; + +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.gpu.VGPUTypesVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +@Local(value = VGPUTypesDao.class) +public class VGPUTypesDaoImpl extends GenericDaoBase implements VGPUTypesDao { + private static final Logger s_logger = Logger.getLogger(VGPUTypesDaoImpl.class); + + private final SearchBuilder _searchByGroupId; + private final SearchBuilder _searchByGroupIdVGPUType; + // private final SearchBuilder _searchByHostId; + // private final SearchBuilder _searchForStaleEntries; + + @Inject protected HostGpuGroupsDao _hostGpuGroupsDao; + + public VGPUTypesDaoImpl() { + + _searchByGroupId = createSearchBuilder(); + _searchByGroupId.and("groupId", _searchByGroupId.entity().getGpuGroupId(), SearchCriteria.Op.EQ); + _searchByGroupId.done(); + + _searchByGroupIdVGPUType = createSearchBuilder(); + _searchByGroupIdVGPUType.and("groupId", _searchByGroupIdVGPUType.entity().getGpuGroupId(), SearchCriteria.Op.EQ); + _searchByGroupIdVGPUType.and("vgpuType", _searchByGroupIdVGPUType.entity().getVgpuType(), SearchCriteria.Op.EQ); + _searchByGroupIdVGPUType.done(); + } + + @Override + public List listByGroupId(long groupId) { + SearchCriteria sc = _searchByGroupId.create(); + sc.setParameters("groupId", groupId); + return listBy(sc); + } + + @Override + public VGPUTypesVO findByGroupIdVGPUType(long groupId, String vgpuType) { + SearchCriteria sc = _searchByGroupIdVGPUType.create(); + sc.setParameters("groupId", groupId); + sc.setParameters("vgpuType", vgpuType); + return findOneBy(sc); + } + + @Override + public void persist(long hostId, HashMap> groupDetails) { + Iterator>> it1 = groupDetails.entrySet().iterator(); + while (it1.hasNext()) { + Entry> entry = it1.next(); + HostGpuGroupsVO gpuGroup = _hostGpuGroupsDao.findByHostIdGroupName(hostId, entry.getKey()); + HashMap values = entry.getValue(); + Iterator> it2 = values.entrySet().iterator(); + while (it2.hasNext()) { + Entry record = it2.next(); + VGPUTypesVO vgpuType = null; + if ((vgpuType = findByGroupIdVGPUType(gpuGroup.getId(), record.getKey())) == null) { + persist(new VGPUTypesVO(record.getKey(), gpuGroup.getId(), record.getValue())); + } else { + vgpuType.setRemainingCapacity(record.getValue()); + update(vgpuType.getId(), vgpuType); + } + } + } + } +} diff --git a/engine/schema/src/com/cloud/host/HostVO.java b/engine/schema/src/com/cloud/host/HostVO.java index 56c066b1b72..25cc9544da2 100755 --- a/engine/schema/src/com/cloud/host/HostVO.java +++ b/engine/schema/src/com/cloud/host/HostVO.java @@ -17,6 +17,7 @@ package com.cloud.host; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -153,6 +154,10 @@ public class HostVO implements Host { @Transient List hostTags; + // This value is only for saving and current cannot be loaded. + @Transient + HashMap> groupDetails = new HashMap>(); + @Override public String getStorageIpAddressDeux() { return storageIpAddressDeux; @@ -313,6 +318,14 @@ public class HostVO implements Host { this.hostTags = hostTags; } + public HashMap> getGpuGroupDetails() { + return groupDetails; + } + + public void setGpuGroups(HashMap> groupDetails) { + this.groupDetails = groupDetails; + } + @Column(name = "data_center_id", nullable = false) private long dataCenterId; diff --git a/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java index 08a436644ba..c2a9826405d 100755 --- a/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java +++ b/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java @@ -38,6 +38,8 @@ import com.cloud.cluster.agentlb.HostTransferMapVO; import com.cloud.cluster.agentlb.dao.HostTransferMapDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.dao.ClusterDao; +import com.cloud.gpu.dao.HostGpuGroupsDao; +import com.cloud.gpu.dao.VGPUTypesDao; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.HostTagVO; @@ -126,6 +128,10 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao @Inject protected HostDetailsDao _detailsDao; @Inject + protected HostGpuGroupsDao _hostGpuGroupsDao; + @Inject + protected VGPUTypesDao _vgpuTypesDao; + @Inject protected HostTagsDao _hostTagsDao; @Inject protected HostTransferMapDao _hostTransferDao; @@ -775,6 +781,16 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao _hostTagsDao.persist(host.getId(), hostTags); } + protected void saveGpuRecords(HostVO host) { + HashMap> groupDetails = host.getGpuGroupDetails(); + if (groupDetails != null) { + // Create/Update GPU group entries + _hostGpuGroupsDao.persist(host.getId(), new ArrayList(groupDetails.keySet())); + // Create/Update VGPU types entries + _vgpuTypesDao.persist(host.getId(), groupDetails); + } + } + @Override @DB public HostVO persist(HostVO host) { @@ -797,6 +813,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao loadDetails(dbHost); saveHostTags(host); loadHostTags(dbHost); + saveGpuRecords(host); txn.commit(); @@ -816,6 +833,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao saveDetails(host); saveHostTags(host); + saveGpuRecords(host); txn.commit(); diff --git a/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java b/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java index ed8090b3bc7..8dfe5273b94 100644 --- a/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java +++ b/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java @@ -60,6 +60,10 @@ public class ServiceOfferingDetailsVO implements ResourceDetail { return resourceId; } + public void setResourceId(long serviceOfferingId) { + this.resourceId = serviceOfferingId; + } + @Override public String getName() { return name; diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index b1ad63a044c..d80bddc3773 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -56,11 +56,13 @@ import com.trilead.ssh2.SCPClient; import com.xensource.xenapi.Bond; import com.xensource.xenapi.Connection; import com.xensource.xenapi.Console; +import com.xensource.xenapi.GPUGroup; import com.xensource.xenapi.Host; import com.xensource.xenapi.HostCpu; import com.xensource.xenapi.HostMetrics; import com.xensource.xenapi.Network; import com.xensource.xenapi.PBD; +import com.xensource.xenapi.PGPU; import com.xensource.xenapi.PIF; import com.xensource.xenapi.Pool; import com.xensource.xenapi.SR; @@ -73,6 +75,8 @@ import com.xensource.xenapi.Types.XenAPIException; import com.xensource.xenapi.VBD; import com.xensource.xenapi.VBDMetrics; import com.xensource.xenapi.VDI; +import com.xensource.xenapi.VGPU; +import com.xensource.xenapi.VGPUType; import com.xensource.xenapi.VIF; import com.xensource.xenapi.VLAN; import com.xensource.xenapi.VM; @@ -108,6 +112,8 @@ import com.cloud.agent.api.CreateVMSnapshotCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; import com.cloud.agent.api.DeleteVMSnapshotAnswer; import com.cloud.agent.api.DeleteVMSnapshotCommand; +import com.cloud.agent.api.GetGPUStatsAnswer; +import com.cloud.agent.api.GetGPUStatsCommand; import com.cloud.agent.api.GetHostStatsAnswer; import com.cloud.agent.api.GetHostStatsCommand; import com.cloud.agent.api.GetStorageStatsAnswer; @@ -201,6 +207,7 @@ import com.cloud.agent.api.storage.ResizeVolumeCommand; import com.cloud.agent.api.to.DataStoreTO; import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.agent.api.to.IpAddressTO; import com.cloud.agent.api.to.NfsTO; import com.cloud.agent.api.to.NicTO; @@ -433,6 +440,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return execute((GetHostStatsCommand)cmd); } else if (clazz == GetVmStatsCommand.class) { return execute((GetVmStatsCommand)cmd); + } else if (clazz == GetGPUStatsCommand.class) { + return execute((GetGPUStatsCommand) cmd); } else if (clazz == GetVmDiskStatsCommand.class) { return execute((GetVmDiskStatsCommand)cmd); } else if (clazz == CheckHealthCommand.class) { @@ -1288,6 +1297,65 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return dynamicMinRam; } + private HashMap> getGPUGroupDetails(Connection conn) throws XenAPIException, XmlRpcException { + HashMap> groupDetails = new HashMap>(); + Host host = Host.getByUuid(conn, _host.uuid); + Set pgpus = host.getPGPUs(conn); + Iterator iter = pgpus.iterator(); + while (iter.hasNext()) { + PGPU pgpu = iter.next(); + GPUGroup gpuGroup = pgpu.getGPUGroup(conn); + Set enabledVGPUTypes = gpuGroup.getEnabledVGPUTypes(conn); + String groupName = gpuGroup.getNameLabel(conn); + HashMap gpuCapacity = new HashMap(); + if (groupDetails.get(groupName) != null) { + gpuCapacity = groupDetails.get(groupName); + } + // Get remaining capacity of all the enabled VGPU in a PGPU + if(enabledVGPUTypes != null) { + Iterator it = enabledVGPUTypes.iterator(); + while (it.hasNext()) { + VGPUType type = it.next(); + String modelName = type.getModelName(conn); + Long remainingCapacity = pgpu.getRemainingCapacity(conn, type); + if (gpuCapacity.get(modelName) != null) { + long newRemainingCapacity = gpuCapacity.get(modelName) + remainingCapacity; + gpuCapacity.put(modelName, newRemainingCapacity); + } else { + gpuCapacity.put(modelName, remainingCapacity); + } + } + } + groupDetails.put(groupName, gpuCapacity); + } + return groupDetails; + } + + protected void createVGPU(Connection conn, StartCommand cmd, VM vm, GPUDeviceTO gpuDevice) throws XenAPIException, XmlRpcException { + Set groups = GPUGroup.getByNameLabel(conn, gpuDevice.getGpuGroup()); + assert groups.size() == 1 : "Should only have 1 group but found " + groups.size(); + GPUGroup gpuGroup = groups.iterator().next(); + + Set vgpuTypes = gpuGroup.getEnabledVGPUTypes(conn); + Iterator iter = vgpuTypes.iterator(); + VGPUType vgpuType = null; + while (iter.hasNext()) { + VGPUType entry = iter.next(); + if (entry.getModelName(conn).equals(gpuDevice.getVgpuType())) { + vgpuType = entry; + } + } + String device = "0"; // Only allow device = "0" for now, as XenServer supports just a single vGPU per VM. + Map other_config = new HashMap(); + VGPU.create(conn, vm, gpuGroup, device, other_config, vgpuType); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Created VGPU of VGPU type [ " + gpuDevice.getVgpuType() + " ] for VM " + cmd.getVirtualMachine().getName()); + } + // Calculate and set remaining GPU capacity in the host. + cmd.getVirtualMachine().getGpuDevice().setGroupDetails(getGPUGroupDetails(conn)); + } + protected VM createVmFromTemplate(Connection conn, VirtualMachineTO vmSpec, Host host) throws XenAPIException, XmlRpcException { String guestOsTypeName = getGuestOsType(vmSpec.getOs(), vmSpec.getBootloader() == BootloaderType.CD); Set templates = VM.getByNameLabel(conn, guestOsTypeName); @@ -1721,6 +1789,13 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe Host host = Host.getByUuid(conn, _host.uuid); vm = createVmFromTemplate(conn, vmSpec, host); + GPUDeviceTO gpuDevice = vmSpec.getGpuDevice(); + if (gpuDevice != null) { + s_logger.debug("Creating VGPU for of VGPU type: " + gpuDevice.getVgpuType() + " in GPU group " + + gpuDevice.getGpuGroup() + " for VM " + vmName ); + createVGPU(conn, cmd, vm, gpuDevice); + } + for (DiskTO disk : vmSpec.getDisks()) { VDI newVdi = prepareManagedDisk(conn, disk, vmName); @@ -2245,6 +2320,18 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return hostStats; } + protected GetGPUStatsAnswer execute(GetGPUStatsCommand cmd) { + Connection conn = getConnection(); + HashMap> groupDetails = new HashMap>(); + try { + groupDetails = getGPUGroupDetails(conn); + } catch (Exception e) { + String msg = "Unable to get GPU stats" + e.toString(); + s_logger.warn(msg, e); + } + return new GetGPUStatsAnswer(cmd, groupDetails); + } + protected GetVmStatsAnswer execute(GetVmStatsCommand cmd) { Connection conn = getConnection(); List vmNames = cmd.getVmNames(); @@ -3566,6 +3653,18 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe try { if (vm.getPowerState(conn) == VmPowerState.HALTED) { + Set vGPUs = null; + // Get updated GPU details + try { + vGPUs = vm.getVGPUs(conn); + } catch (XenAPIException e2) { + s_logger.debug("VM " + vmName + " does not have GPU support."); + } + if (vGPUs != null && !vGPUs.isEmpty()) { + HashMap> groupDetails = getGPUGroupDetails(conn); + cmd.setGpuDevice(new GPUDeviceTO(null, null, groupDetails)); + } + Set vifs = vm.getVIFs(conn); List networks = new ArrayList(); for (VIF vif : vifs) { @@ -5400,6 +5499,15 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe details.put("private.network.device", _privateNetworkName); } + try { + HashMap> groupDetails = getGPUGroupDetails(conn); + cmd.setGpuGroupDetails(groupDetails); + } catch (Exception e) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("GPU device not found in host " + hr.hostname); + } + } + cmd.setHostDetails(details); cmd.setName(hr.nameLabel); cmd.setGuid(_host.uuid); diff --git a/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java index b77f8acce4d..8abb5cd316f 100755 --- a/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java +++ b/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java @@ -26,11 +26,10 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; - import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.capacity.CapacityManager; import com.cloud.dc.ClusterDetailsDao; @@ -38,6 +37,7 @@ import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.gpu.GPU; import com.cloud.host.DetailVO; import com.cloud.host.Host; import com.cloud.host.Host.Type; @@ -47,7 +47,9 @@ import com.cloud.host.dao.HostDetailsDao; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.GuestOSCategoryVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.VMTemplateVO; @@ -99,6 +101,8 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { ClusterDao _clusterDao; @Inject ClusterDetailsDao _clusterDetailsDao; + @Inject + ServiceOfferingDetailsDao _serviceOfferingDetailsDao; float _factor = 1; boolean _checkHvm = true; protected String _allocationAlgorithm = "random"; @@ -264,7 +268,9 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { s_logger.debug("Looking for speed=" + (offering.getCpu() * offering.getSpeed()) + "Mhz, Ram=" + offering.getRamSize()); } + long serviceOfferingId = offering.getId(); List suitableHosts = new ArrayList(); + ServiceOfferingDetailsVO offeringDetails = null; for (Host host : hosts) { if (suitableHosts.size() == returnUpTo) { @@ -286,6 +292,13 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { continue; } + // Check if GPU device is required by offering and host has the availability + if ((offeringDetails = _serviceOfferingDetailsDao.findDetail(serviceOfferingId, GPU.Keys.vgpuType.toString())) != null + && !_resourceMgr.isGPUDeviceAvailable(host.getId(), offeringDetails.getValue())){ + s_logger.info("Host name: " + host.getName() + ", hostId: "+ host.getId() +" does not have required GPU devices available"); + continue; + } + int cpu_requested = offering.getCpu() * offering.getSpeed(); long ram_requested = offering.getRamSize() * 1024L * 1024L; Cluster cluster = _clusterDao.findById(host.getClusterId()); diff --git a/server/src/com/cloud/api/ApiDBUtils.java b/server/src/com/cloud/api/ApiDBUtils.java index a23244b86f0..539eb704005 100755 --- a/server/src/com/cloud/api/ApiDBUtils.java +++ b/server/src/com/cloud/api/ApiDBUtils.java @@ -130,6 +130,10 @@ import com.cloud.domain.dao.DomainDao; import com.cloud.event.Event; import com.cloud.event.dao.EventJoinDao; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.gpu.VGPUTypesVO; +import com.cloud.gpu.dao.HostGpuGroupsDao; +import com.cloud.gpu.dao.VGPUTypesDao; import com.cloud.ha.HighAvailabilityManager; import com.cloud.host.Host; import com.cloud.host.HostStats; @@ -221,8 +225,10 @@ import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.StatsCollector; import com.cloud.server.TaggedResourceService; +import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSCategoryVO; @@ -325,6 +331,7 @@ public class ApiDBUtils { static NetworkRuleConfigDao s_networkRuleConfigDao; static HostPodDao s_podDao; static ServiceOfferingDao s_serviceOfferingDao; + static ServiceOfferingDetailsDao s_serviceOfferingDetailsDao; static SnapshotDao s_snapshotDao; static PrimaryDataStoreDao s_storagePoolDao; static VMTemplateDao s_templateDao; @@ -400,6 +407,8 @@ public class ApiDBUtils { static NetworkACLDao s_networkACLDao; static AccountService s_accountService; static ResourceMetaDataService s_resourceDetailsService; + static HostGpuGroupsDao s_hostGpuGroupsDao; + static VGPUTypesDao s_vgpuTypesDao; @Inject private ManagementServer ms; @@ -467,6 +476,8 @@ public class ApiDBUtils { @Inject private ServiceOfferingDao serviceOfferingDao; @Inject + private ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject private SnapshotDao snapshotDao; @Inject private PrimaryDataStoreDao storagePoolDao; @@ -616,6 +627,10 @@ public class ApiDBUtils { private ConfigurationManager configMgr; @Inject private ResourceMetaDataService resourceDetailsService; + @Inject + private HostGpuGroupsDao hostGpuGroupsDao; + @Inject + private VGPUTypesDao vgpuTypesDao; @PostConstruct void init() { @@ -649,6 +664,7 @@ public class ApiDBUtils { s_networkRuleConfigDao = networkRuleConfigDao; s_podDao = podDao; s_serviceOfferingDao = serviceOfferingDao; + s_serviceOfferingDetailsDao = serviceOfferingDetailsDao; s_serviceOfferingJoinDao = serviceOfferingJoinDao; s_snapshotDao = snapshotDao; s_storagePoolDao = storagePoolDao; @@ -727,6 +743,8 @@ public class ApiDBUtils { s_networkACLDao = networkACLDao; s_accountService = accountService; s_resourceDetailsService = resourceDetailsService; + s_hostGpuGroupsDao = hostGpuGroupsDao; + s_vgpuTypesDao = vgpuTypesDao; } @@ -960,6 +978,10 @@ public class ApiDBUtils { return s_serviceOfferingDao.findByIdIncludingRemoved(serviceOfferingId); } + public static ServiceOfferingDetailsVO findServiceOfferingDetail(long serviceOfferingId, String key) { + return s_serviceOfferingDetailsDao.findDetail(serviceOfferingId, key); + } + public static Snapshot findSnapshotById(long snapshotId) { SnapshotVO snapshot = s_snapshotDao.findById(snapshotId); if (snapshot != null && snapshot.getRemoved() == null && snapshot.getState() == Snapshot.State.BackedUp) { @@ -1057,6 +1079,14 @@ public class ApiDBUtils { return type; } + public static List getGpuGroups(long hostId) { + return s_hostGpuGroupsDao.listByHostId(hostId); + } + + public static List getVgpus(long groupId) { + return s_vgpuTypesDao.listByGroupId(groupId); + } + public static List listUserStatsBy(Long accountId) { return s_userStatsDao.listBy(accountId); } diff --git a/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java index 1b95d9b24cc..6c890fc830c 100644 --- a/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java @@ -31,12 +31,16 @@ import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.apache.cloudstack.api.ApiConstants.HostDetails; +import org.apache.cloudstack.api.response.GpuResponse; import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.VgpuResponse; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.HostJoinVO; +import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.gpu.VGPUTypesVO; import com.cloud.host.Host; import com.cloud.host.HostStats; import com.cloud.storage.StorageStats; @@ -92,6 +96,27 @@ public class HostJoinDaoImpl extends GenericDaoBase implements hostResponse.setVersion(host.getVersion()); hostResponse.setCreated(host.getCreated()); + List gpuGroups = ApiDBUtils.getGpuGroups(host.getId()); + if (gpuGroups != null && !gpuGroups.isEmpty()) { + List gpus = new ArrayList(); + for (HostGpuGroupsVO entry : gpuGroups) { + GpuResponse gpuResponse = new GpuResponse(); + gpuResponse.setGpuGroupName(entry.getGroupName()); + List vgpuTypes = ApiDBUtils.getVgpus(entry.getId()); + if (vgpuTypes != null && !vgpuTypes.isEmpty()) { + List vgpus = new ArrayList(); + for (VGPUTypesVO vgpuType : vgpuTypes) { + VgpuResponse vgpuResponse = new VgpuResponse(); + vgpuResponse.setName(vgpuType.getVgpuType()); + vgpuResponse.setCapacity(vgpuType.getRemainingCapacity()); + vgpus.add(vgpuResponse); + } + gpuResponse.setVgpu(vgpus); + } + gpus.add(gpuResponse); + } + hostResponse.setGpuGroups(gpus); + } if (details.contains(HostDetails.all) || details.contains(HostDetails.capacity) || details.contains(HostDetails.stats) || details.contains(HostDetails.events)) { hostResponse.setOsCategoryId(host.getOsCategoryUuid()); diff --git a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 08478e2958b..235902cc7bd 100644 --- a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -40,7 +40,9 @@ import org.springframework.stereotype.Component; import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.ResourceTagJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.gpu.GPU; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.user.Account; import com.cloud.uservm.UserVm; import com.cloud.utils.db.GenericDaoBase; @@ -157,6 +159,10 @@ public class UserVmJoinDaoImpl extends GenericDaoBase implem userVmResponse.setCpuNumber(userVm.getCpu()); userVmResponse.setCpuSpeed(userVm.getSpeed()); userVmResponse.setMemory(userVm.getRamSize()); + ServiceOfferingDetailsVO serviceOfferingDetail = ApiDBUtils.findServiceOfferingDetail(userVm.getServiceOfferingId(), GPU.Keys.vgpuType.toString()); + if (serviceOfferingDetail != null) { + userVmResponse.setVgpu(serviceOfferingDetail.getValue()); + } } userVmResponse.setGuestOsId(userVm.getGuestOsUuid()); if (details.contains(VMDetails.all) || details.contains(VMDetails.volume)) { diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 08cc5a63f50..863d71b843b 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -130,6 +130,7 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.gpu.GPU; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.IpAddressManager; import com.cloud.network.Network; @@ -2079,13 +2080,56 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati offering.setHypervisorSnapshotReserve(hypervisorSnapshotReserve); - if ((offering = _serviceOfferingDao.persist(offering)) != null) { - if (details != null) { - List detailsVO = new ArrayList(); - for (Entry detailEntry : details.entrySet()) { - detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), detailEntry.getKey(), detailEntry.getValue(), true)); + List detailsVO = null; + if (details != null) { + // Check if the user has passed the gpu-type before passing the VGPU type + if (!details.containsKey(GPU.Keys.pciDevice.toString()) && details.containsKey(GPU.Keys.vgpuType.toString())) { + throw new InvalidParameterValueException("Please specify the gpu type"); + } + detailsVO = new ArrayList(); + for (Entry detailEntry : details.entrySet()) { + String value = null; + if (detailEntry.getKey().equals(GPU.Keys.pciDevice.toString())) { + for (GPU.Type type : GPU.Type.values()) { + if (detailEntry.getValue().equals(type.toString())) { + value = detailEntry.getValue(); + } + } + if (value == null) { + throw new InvalidParameterValueException("Please specify valid gpu type"); + } } + if (detailEntry.getKey().equals(GPU.Keys.vgpuType.toString())) { + if (details.get(GPU.Keys.pciDevice.toString()).equals(GPU.Type.GPU_Passthrough.toString())) { + throw new InvalidParameterValueException("vgpuTypes are supported only with vGPU pciDevice"); + } + if (detailEntry.getValue() == null) { + throw new InvalidParameterValueException("With vGPU as pciDevice, vGPUType value cannot be null"); + } + for (GPU.vGPUType entry : GPU.vGPUType.values()) { + if (detailEntry.getValue().equals(entry.getType())) { + value = entry.getType(); + } + } + if (value == null || detailEntry.getValue().equals(GPU.vGPUType.passthrough.getType())) { + throw new InvalidParameterValueException("Please specify valid vGPU type"); + } + } + detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), detailEntry.getKey(), detailEntry.getValue(), true)); + } + // If pciDevice type is passed, put the default VGPU type as 'passthrough' + if (details.containsKey(GPU.Keys.pciDevice.toString()) + && !details.containsKey(GPU.Keys.vgpuType.toString())) { + detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), + GPU.Keys.vgpuType.toString(), GPU.vGPUType.passthrough.getType(), true)); + } + } + if ((offering = _serviceOfferingDao.persist(offering)) != null) { + if (detailsVO != null && !detailsVO.isEmpty()) { + for (int index = 0; index < detailsVO.size(); index++) { + detailsVO.get(index).setResourceId(offering.getId()); + } _serviceOfferingDetailsDao.saveDetails(detailsVO); } CallContext.current().setEventDetails("Service offering id=" + offering.getId()); diff --git a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 3c87b249990..f76e4852849 100644 --- a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -81,6 +81,7 @@ import com.cloud.deploy.dao.PlannerHostReservationDao; import com.cloud.exception.AffinityConflictException; import com.cloud.exception.ConnectionException; import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.gpu.GPU; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; @@ -89,7 +90,10 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.org.Grouping; +import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage; @@ -213,6 +217,10 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy DataStoreManager dataStoreMgr; @Inject protected ClusterDetailsDao _clusterDetailsDao; + @Inject + protected ResourceManager _resourceMgr; + @Inject + protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao; protected List _planners; @@ -353,6 +361,7 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy s_logger.debug("This VM has last host_id specified, trying to choose the same host: " + vm.getLastHostId()); HostVO host = _hostDao.findById(vm.getLastHostId()); + ServiceOfferingDetailsVO offeringDetails = null; if (host == null) { s_logger.debug("The last host of this VM cannot be found"); } else if (avoids.shouldAvoid(host)) { @@ -360,6 +369,9 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy } else if (_capacityMgr.checkIfHostReachMaxGuestLimit(host)) { s_logger.debug("The last Host, hostId: " + host.getId() + " already has max Running VMs(count includes system VMs), skipping this and trying other available hosts"); + } else if ((offeringDetails = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString())) != null + && !_resourceMgr.isGPUDeviceAvailable(host.getId(), offeringDetails.getValue())){ + s_logger.debug("The last host of this VM does not have required GPU devices available"); } else { if (host.getStatus() == Status.Up && host.getResourceState() == ResourceState.Enabled) { boolean hostTagsMatch = true; @@ -1397,4 +1409,4 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy } return true; } -} \ No newline at end of file +} diff --git a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java index 7fb79fa1578..4af20c6f00d 100644 --- a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java @@ -25,8 +25,12 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.gpu.GPU; import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; import com.cloud.server.ConfigurationServer; +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; @@ -55,6 +59,10 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis NicSecondaryIpDao _nicSecIpDao; @Inject ConfigurationServer _configServer; + @Inject + ResourceManager _resourceMgr; + @Inject + ServiceOfferingDetailsDao _serviceOfferingDetailsDao; protected HypervisorGuruBase() { super(); @@ -125,6 +133,13 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis if (detailsInVm != null) { to.setDetails(detailsInVm); } + + // Set GPU details + ServiceOfferingDetailsVO offeringDetail = null; + if ((offeringDetail = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString())) != null) { + to.setGpuDevice(_resourceMgr.getGPUDevice(vm.getHostId(), offeringDetail.getValue())); + } + // Workaround to make sure the TO has the UUID we need for Niciri integration VMInstanceVO vmInstance = _virtualMachineDao.findById(to.getId()); // check if XStools/VMWare tools are present in the VM and dynamic scaling feature is enabled (per zone/global) diff --git a/server/src/com/cloud/network/NetworkUsageManagerImpl.java b/server/src/com/cloud/network/NetworkUsageManagerImpl.java index e9b039369aa..13eb2107e8b 100755 --- a/server/src/com/cloud/network/NetworkUsageManagerImpl.java +++ b/server/src/com/cloud/network/NetworkUsageManagerImpl.java @@ -57,6 +57,7 @@ import com.cloud.event.UsageEventVO; import com.cloud.event.dao.UsageEventDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.gpu.dao.HostGpuGroupsDao; import com.cloud.host.DetailVO; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -116,6 +117,8 @@ public class NetworkUsageManagerImpl extends ManagerBase implements NetworkUsage @Inject HostDetailsDao _detailsDao; @Inject + HostGpuGroupsDao _hostGpuGroupsDao; + @Inject AccountManager _accountMgr; @Inject NetworkDao _networksDao = null; @@ -537,6 +540,7 @@ public class NetworkUsageManagerImpl extends ManagerBase implements NetworkUsage long hostId = host.getId(); _agentMgr.disconnectWithoutInvestigation(hostId, Status.Event.Remove); _detailsDao.deleteDetails(hostId); + _hostGpuGroupsDao.deleteGpuEntries(hostId); host.setGuid(null); _hostDao.update(hostId, host); _hostDao.remove(hostId); diff --git a/server/src/com/cloud/resource/ResourceManagerImpl.java b/server/src/com/cloud/resource/ResourceManagerImpl.java index adad85ca8a6..26258852b17 100755 --- a/server/src/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/com/cloud/resource/ResourceManagerImpl.java @@ -30,11 +30,6 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import com.google.gson.Gson; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd; import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd; @@ -51,10 +46,14 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.lang.ObjectUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; +import com.cloud.agent.api.GetGPUStatsAnswer; +import com.cloud.agent.api.GetGPUStatsCommand; import com.cloud.agent.api.GetHostStatsAnswer; import com.cloud.agent.api.GetHostStatsCommand; import com.cloud.agent.api.MaintainAnswer; @@ -64,6 +63,7 @@ import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.UnsupportedAnswer; import com.cloud.agent.api.UpdateHostPasswordCommand; +import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.agent.transport.Request; import com.cloud.api.ApiDBUtils; import com.cloud.capacity.Capacity; @@ -97,6 +97,11 @@ import com.cloud.exception.DiscoveryException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceInUseException; +import com.cloud.gpu.GPU.vGPUType; +import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.gpu.VGPUTypesVO; +import com.cloud.gpu.dao.HostGpuGroupsDao; +import com.cloud.gpu.dao.VGPUTypesDao; import com.cloud.ha.HighAvailabilityManager; import com.cloud.ha.HighAvailabilityManager.WorkType; import com.cloud.host.DetailVO; @@ -137,11 +142,14 @@ import com.cloud.utils.UriUtils; 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.GenericSearchBuilder; import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; @@ -158,6 +166,7 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.VMInstanceDao; +import com.google.gson.Gson; @Component @Local({ResourceManager.class, ResourceService.class}) @@ -193,6 +202,10 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, @Inject private GuestOSCategoryDao _guestOSCategoryDao; @Inject + protected HostGpuGroupsDao _hostGpuGroupsDao; + @Inject + protected VGPUTypesDao _vgpuTypesDao; + @Inject private PrimaryDataStoreDao _storagePoolDao; @Inject private DataCenterIpAddressDao _privateIPAddressDao; @@ -244,6 +257,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, private GenericSearchBuilder _hypervisorsInDC; + private SearchBuilder _gpuAvailability; + private void insertListener(Integer event, ResourceListener listener) { List lst = _lifeCycleListeners.get(event); if (lst == null) { @@ -827,6 +842,9 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, // delete host details _hostDetailsDao.deleteDetails(hostId); + // if host is GPU enabled, delete GPU entries + _hostGpuGroupsDao.deleteGpuEntries(hostId); + host.setGuid(null); Long clusterId = host.getClusterId(); host.setClusterId(null); @@ -1329,6 +1347,14 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, _hypervisorsInDC.and("type", _hypervisorsInDC.entity().getType(), SearchCriteria.Op.EQ); _hypervisorsInDC.done(); + _gpuAvailability = _hostGpuGroupsDao.createSearchBuilder(); + _gpuAvailability.and("hostId", _gpuAvailability.entity().getHostId(), Op.EQ); + SearchBuilder join1 = _vgpuTypesDao.createSearchBuilder(); + join1.and("vgpuType", join1.entity().getVgpuType(), Op.EQ); + join1.and("remainingCapacity", join1.entity().getRemainingCapacity(), Op.GT); + _gpuAvailability.join("groupId", join1, _gpuAvailability.entity().getId(), join1.entity().getGpuGroupId(), JoinBuilder.JoinType.INNER); + _gpuAvailability.done(); + return true; } @@ -1958,6 +1984,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, host.setSpeed(ssCmd.getSpeed()); host.setHypervisorType(hyType); host.setHypervisorVersion(ssCmd.getHypervisorVersion()); + host.setGpuGroups(ssCmd.getGpuGroupDetails()); return host; } @@ -2473,6 +2500,66 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, return sc.list(); } + @Override + public List listAvailableGPUDevice(long hostId, String vgpuType) { + if (vgpuType == null) { + vgpuType = vGPUType.passthrough.getType(); + } + Filter searchFilter = new Filter(VGPUTypesVO.class, "remainingCapacity", false, null, null); + SearchCriteria sc = _gpuAvailability.create(); + sc.setParameters("hostId", hostId); + sc.setJoinParameters("groupId", "vgpuType", vgpuType); + sc.setJoinParameters("groupId", "remainingCapacity", 0); + return _hostGpuGroupsDao.customSearch(sc, searchFilter); + } + + @Override + public boolean isGPUDeviceAvailable(long hostId, String vgpuType) { + if(!listAvailableGPUDevice(hostId, vgpuType).isEmpty()) { + return true; + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Host ID: "+ hostId +" does not have GPU device available"); + } + return false; + } + } + + @Override + public GPUDeviceTO getGPUDevice(long hostId, String vgpuType) { + HostGpuGroupsVO gpuDevice = listAvailableGPUDevice(hostId, vgpuType).get(0); + return new GPUDeviceTO(gpuDevice.getGroupName(), vgpuType, null); + } + + @Override + public void updateGPUDetails(long hostId, HashMap> groupDetails) { + // Update GPU group capacity + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + _hostGpuGroupsDao.persist(hostId, new ArrayList(groupDetails.keySet())); + _vgpuTypesDao.persist(hostId, groupDetails); + txn.commit(); + } + + @Override + public HashMap> getGPUStatistics(HostVO host) { + Answer answer = _agentMgr.easySend(host.getId(), new GetGPUStatsCommand(host.getGuid(), host.getName())); + if (answer != null && (answer instanceof UnsupportedAnswer)) { + return null; + } + if (answer == null || !answer.getResult()) { + String msg = "Unable to obtain GPU stats for host " + host.getName(); + s_logger.warn(msg); + return null; + } else { + // now construct the result object + if (answer instanceof GetGPUStatsAnswer) { + return ((GetGPUStatsAnswer)answer).getGroupDetails(); + } + } + return null; + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_HOST_RESERVATION_RELEASE, eventDescription = "releasing host reservation", async = true) diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 49a9eb53e5d..663d4e505c5 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -508,6 +508,7 @@ import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; +import com.cloud.gpu.GPU; import com.cloud.ha.HighAvailabilityManager; import com.cloud.host.DetailVO; import com.cloud.host.Host; @@ -539,6 +540,7 @@ import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.auth.UserAuthenticator; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSCategoryVO; @@ -700,6 +702,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe private ResourceTagDao _resourceTagDao; @Inject private ImageStoreDao _imgStoreDao; + @Inject + private ServiceOfferingDetailsDao _serviceOfferingDetailsDao; + @Inject private ProjectManager _projectMgr; @@ -1059,6 +1064,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe throw ex; } + if(_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { + s_logger.info(" Live Migration of GPU enabled VM : " + vm.getInstanceName()+ " is not supported"); + // Return empty list. + return new Ternary, Integer>, List, Map>(new Pair, + Integer>(new ArrayList(), new Integer(0)), new ArrayList(), new HashMap()); + } + if (!vm.getHypervisorType().equals(HypervisorType.XenServer) && !vm.getHypervisorType().equals(HypervisorType.VMware) && !vm.getHypervisorType().equals(HypervisorType.KVM) && !vm.getHypervisorType().equals(HypervisorType.Ovm) && !vm.getHypervisorType().equals(HypervisorType.Hyperv)) { if (s_logger.isDebugEnabled()) { diff --git a/server/src/com/cloud/server/StatsCollector.java b/server/src/com/cloud/server/StatsCollector.java index 548587cfa8e..067ed003f39 100755 --- a/server/src/com/cloud/server/StatsCollector.java +++ b/server/src/com/cloud/server/StatsCollector.java @@ -54,6 +54,7 @@ import com.cloud.agent.api.VmStatsEntry; import com.cloud.cluster.ManagementServerHostVO; import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.exception.StorageUnavailableException; +import com.cloud.gpu.dao.HostGpuGroupsDao; import com.cloud.host.Host; import com.cloud.host.HostStats; import com.cloud.host.HostVO; @@ -175,6 +176,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc private AutoScaleVmProfileDao _asProfileDao; @Inject private ServiceOfferingDao _serviceOfferingDao; + @Inject + private HostGpuGroupsDao _hostGpuGroupsDao; private ConcurrentHashMap _hostStats = new ConcurrentHashMap(); private final ConcurrentHashMap _VmStats = new ConcurrentHashMap(); @@ -188,6 +191,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc long volumeStatsInterval = -1L; long autoScaleStatsInterval = -1L; int vmDiskStatsInterval = 0; + List hostIds = null; private ScheduledExecutorService _diskStatsUpdateExecutor; private int _usageAggregationRange = 1440; @@ -325,6 +329,23 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } } _hostStats = hostStats; + // Get a subset of hosts with GPU support from the list of "hosts" + List gpuEnabledHosts = new ArrayList(); + if (hostIds != null) { + for (HostVO host : hosts) { + if (hostIds.contains(host.getId())) { + gpuEnabledHosts.add(host); + } + } + } else { + // Check for all the hosts managed by CloudStack. + gpuEnabledHosts = hosts; + } + for (HostVO host : gpuEnabledHosts) { + HashMap> groupDetails = _resourceMgr.getGPUStatistics(host); + _resourceMgr.updateGPUDetails(host.getId(), groupDetails); + } + hostIds = _hostGpuGroupsDao.listHostIds(); } catch (Throwable t) { s_logger.error("Error trying to retrieve host stats", t); } diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index 17461c08774..acc922fab40 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -92,12 +92,14 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageUnavailableException; +import com.cloud.gpu.GPU; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorCapabilitiesVO; import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.org.Grouping; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.SnapshotDao; @@ -171,6 +173,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic @Inject private SnapshotDao _snapshotDao; @Inject + protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao; + @Inject StoragePoolDetailsDao storagePoolDetailsDao; @Inject private UserVmDao _userVmDao; @@ -1466,6 +1470,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } if (vm != null && vm.getState() == State.Running) { + // Check if the VM is GPU enabled. + if(_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { + throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); + } // Check if the underlying hypervisor supports storage motion. Long hostId = vm.getHostId(); if (hostId != null) { diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index d1df3c11a61..be00aa8c569 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -137,6 +137,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; +import com.cloud.gpu.GPU; import com.cloud.ha.HighAvailabilityManager; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -3853,6 +3854,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir ex.addProxyObject(vm.getUuid(), "vmId"); throw ex; } + + if(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { + throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); + } + if (!vm.getHypervisorType().equals(HypervisorType.XenServer) && !vm.getHypervisorType().equals(HypervisorType.VMware) && !vm.getHypervisorType().equals(HypervisorType.KVM) && !vm.getHypervisorType().equals(HypervisorType.Ovm) && !vm.getHypervisorType().equals(HypervisorType.Hyperv) && !vm.getHypervisorType().equals(HypervisorType.Simulator)) { @@ -4164,6 +4170,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw ex; } + if(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { + throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); + } + if (!vm.getHypervisorType().equals(HypervisorType.XenServer) && !vm.getHypervisorType().equals(HypervisorType.VMware) && !vm.getHypervisorType().equals(HypervisorType.KVM) && !vm.getHypervisorType().equals(HypervisorType.Ovm) && !vm.getHypervisorType().equals(HypervisorType.Simulator)) { throw new InvalidParameterValueException("Unsupported hypervisor type for vm migration, we support" + " XenServer/VMware/KVM only"); diff --git a/server/test/com/cloud/resource/MockResourceManagerImpl.java b/server/test/com/cloud/resource/MockResourceManagerImpl.java index 5599e8c42b9..e6bf9a26b2c 100644 --- a/server/test/com/cloud/resource/MockResourceManagerImpl.java +++ b/server/test/com/cloud/resource/MockResourceManagerImpl.java @@ -17,6 +17,7 @@ package com.cloud.resource; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,6 +36,7 @@ import org.apache.cloudstack.api.command.admin.host.UpdateHostPasswordCmd; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.PodCluster; @@ -42,6 +44,7 @@ import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.DiscoveryException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceInUseException; +import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.HostStats; @@ -554,4 +557,32 @@ public class MockResourceManagerImpl extends ManagerBase implements ResourceMana return false; } + @Override + public boolean isGPUDeviceAvailable(long hostId, String vgpuType) { + // TODO Auto-generated method stub + return false; + } + + @Override + public GPUDeviceTO getGPUDevice(long hostId, String vgpuType) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listAvailableGPUDevice(long hostId, String vgpuType) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void updateGPUDetails(long hostId, HashMap> deviceDetails) { + // TODO Auto-generated method stub + } + + @Override + public HashMap> getGPUStatistics(HostVO host) { + // TODO Auto-generated method stub + return null; + } } diff --git a/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java b/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java index 751a3bdd43f..fb63766e378 100644 --- a/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java +++ b/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java @@ -78,7 +78,9 @@ import com.cloud.exception.AffinityConflictException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.StorageManager; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -238,6 +240,16 @@ public class DeploymentPlanningManagerImplTest { return Mockito.mock(ClusterDetailsDao.class); } + @Bean + public ResourceManager resourceManager() { + return Mockito.mock(ResourceManager.class); + } + + @Bean + public ServiceOfferingDetailsDao serviceOfferingDetailsDao() { + return Mockito.mock(ServiceOfferingDetailsDao.class); + } + @Bean public DataStoreManager cataStoreManager() { return Mockito.mock(DataStoreManager.class); diff --git a/server/test/resources/createNetworkOffering.xml b/server/test/resources/createNetworkOffering.xml index c6228dab707..6ae1978f40f 100644 --- a/server/test/resources/createNetworkOffering.xml +++ b/server/test/resources/createNetworkOffering.xml @@ -43,7 +43,9 @@ - + + + diff --git a/setup/db/db/schema-430to440.sql b/setup/db/db/schema-430to440.sql index be49b838a85..ee4cf215fb7 100644 --- a/setup/db/db/schema-430to440.sql +++ b/setup/db/db/schema-430to440.sql @@ -592,4 +592,22 @@ CREATE VIEW `cloud`.`event_view` AS `cloud`.`event` eve ON event.start_id = eve.id; +DROP TABLE IF EXISTS `cloud`.`host_gpu_groups`; +CREATE TABLE `cloud`.`host_gpu_groups` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `group_name` varchar(255) NOT NULL, + `host_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_host_gpu_groups__host_id` FOREIGN KEY (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB CHARSET=utf8; + +DROP TABLE IF EXISTS `cloud`.`vgpu_types`; +CREATE TABLE `cloud`.`vgpu_types` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `gpu_group_id` bigint(20) unsigned NOT NULL, + `vgpu_type` varchar(40) NOT NULL COMMENT 'vgpu type supported by this gpu group', + `remaining_vm_capacity` bigint(20) unsigned DEFAULT NULL COMMENT 'remaining vgpu can be created with this vgpu_type on the given gpu group', + PRIMARY KEY (`id`), + CONSTRAINT `fk_vgpu_types__gpu_group_id` FOREIGN KEY (`gpu_group_id`) REFERENCES `host_gpu_groups` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB CHARSET=utf8; diff --git a/test/integration/smoke/test_deploy_vgpu_enabled_vm.py b/test/integration/smoke/test_deploy_vgpu_enabled_vm.py new file mode 100644 index 00000000000..a09e87e6f2d --- /dev/null +++ b/test/integration/smoke/test_deploy_vgpu_enabled_vm.py @@ -0,0 +1,227 @@ +# 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. + +#Test from the Marvin - Testing in Python wiki + +#All tests inherit from cloudstackTestCase +from marvin.cloudstackTestCase import cloudstackTestCase + +#Import Integration Libraries + +#base - contains all resources as entities and defines create, delete, list operations on them +from marvin.integration.lib.base import Account, VirtualMachine, ServiceOffering + +#utils - utility classes for common cleanup, external library wrappers etc +from marvin.integration.lib.utils import cleanup_resources + +#common - commonly used methods for all tests are listed here +from marvin.integration.lib.common import get_zone, get_domain, get_template + +from nose.plugins.attrib import attr + +class Services: + """Test VM Life Cycle Services + """ + + def __init__(self): + self.services = { + "disk_offering":{ + "displaytext": "Small", + "name": "Small", + "disksize": 1 + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended in create account to + # ensure unique username generated each time + "password": "password", + }, + "vgpu260q": # Create a virtual machine instance with vgpu type as 260q + { + "displayname": "testserver", + "username": "root", # VM creds for SSH + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "vgpu140q": # Create a virtual machine instance with vgpu type as 140q + { + "displayname": "testserver", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "service_offerings": + { + "vgpu260qwin": + { + "name": "Windows Instance with vGPU260Q", + "displaytext": "Windows Instance with vGPU260Q", + "cpunumber": 2, + "cpuspeed": 1600, # in MHz + "memory": 3072, # In MBs + }, + "vgpu140qwin": + { + # Small service offering ID to for change VM + # service offering from medium to small + "name": "Windows Instance with vGPU140Q", + "displaytext": "Windows Instance with vGPU140Q", + "cpunumber": 2, + "cpuspeed": 1600, + "memory": 3072, + } + }, + "diskdevice": ['/dev/vdc', '/dev/vdb', '/dev/hdb', '/dev/hdc', '/dev/xvdd', '/dev/cdrom', '/dev/sr0', '/dev/cdrom1' ], + # Disk device where ISO is attached to instance + "mount_dir": "/mnt/tmp", + "sleep": 60, + "timeout": 10, + #Migrate VM to hostid + "ostype": 'Windows 7 (32-bit)', + # CentOS 5.3 (64-bit) + } + + +class TestDeployvGPUenabledVM(cloudstackTestCase): + """Test deploy a vGPU enabled VM into a user account + """ + + def setUp(self): + self.services = Services().services + self.apiclient = self.testClient.getApiClient() + + # Get Zone, Domain and Default Built-in template + self.domain = get_domain(self.apiclient, self.services) + self.zone = get_zone(self.apiclient, self.services) + self.services["mode"] = self.zone.networktype + # Before running this test, register a windows template with ostype as 'Windows 7 (32-bit)' + self.template = get_template(self.apiclient, self.zone.id, self.services["ostype"], templatetype='USER') + + #create a user account + self.account = Account.create( + self.apiclient, + self.services["account"], + domainid=self.domain.id + ) + + self.services["vgpu260q"]["zoneid"] = self.zone.id + self.services["vgpu260q"]["template"] = self.template.id + + self.services["vgpu140q"]["zoneid"] = self.zone.id + self.services["vgpu140q"]["template"] = self.template.id + #create a service offering + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["vgpu260qwin"], + serviceofferingdetails={'pciDevice': 'VGPU'} + ) + #build cleanup list + self.cleanup = [ + self.service_offering, + self.account + ] + + @attr(tags = ['advanced', 'simulator', 'basic', 'vgpu']) + def test_deploy_vgpu_enabled_vm(self): + """Test Deploy Virtual Machine + + # Validate the following: + # 1. Virtual Machine is accessible via SSH + # 2. Virtual Machine is vGPU enabled (via SSH) + # 3. listVirtualMachines returns accurate information + """ + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["vgpu260q"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + mode=self.services['mode'] + ) + + list_vms = VirtualMachine.list(self.apiclient, id=self.virtual_machine.id) + + self.debug( + "Verify listVirtualMachines response for virtual machine: %s"\ + % self.virtual_machine.id + ) + + self.assertEqual( + isinstance(list_vms, list), + True, + "List VM response was not a valid list" + ) + self.assertNotEqual( + len(list_vms), + 0, + "List VM response was empty" + ) + + vm = list_vms[0] + self.assertEqual( + vm.id, + self.virtual_machine.id, + "Virtual Machine ids do not match" + ) + self.assertEqual( + vm.name, + self.virtual_machine.name, + "Virtual Machine names do not match" + ) + self.assertEqual( + vm.state, + "Running", + msg="VM is not in Running state" + ) + list_hosts = list_hosts( + self.apiclient, + id=vm.hostid + ) + hostip = list_hosts[0].ipaddress + try: + sshClient = SshClient(host=hostip, port=22, user='root',passwd=self.services["host_password"]) + res = sshClient.execute("xe vgpu-list vm-name-label=%s params=type-uuid %s" % ( + vm.instancename + )) + self.debug("SSH result: %s" % res) + except Exception as e: + self.fail("SSH Access failed for %s: %s" % \ + (hostip, e) + ) + result = str(res) + self.assertEqual( + result.count("type-uuid"), + 1, + "VM is vGPU enabled." + ) + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + self.debug("Warning! Exception in tearDown: %s" % e) \ No newline at end of file diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index 7449d8c9d11..27a26b807d6 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -1462,6 +1462,9 @@ class ServiceOffering: if "deploymentplanner" in services: cmd.deploymentplanner = services["deploymentplanner"] + if "serviceofferingdetails" in services: + cmd.serviceofferingdetails.append({services['serviceofferingdetails']}) + if "isvolatile" in services: cmd.isvolatile = services["isvolatile"] diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index e3c35af3859..86660420fb4 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -361,6 +361,71 @@ } }, + pciDevice: { + label: 'GPU Type', + select: function(args) { + var items = []; + items.push({ + id: '', + description: '' + }); + items.push({ + id: 'GPU_Passthrough', + description: 'GPU-Passthrough' + }); + items.push({ + id: 'VGPU', + description: 'VGPU' + }); + args.response.success({ + data: items + }); + args.$select.change(function() { + var $form = $(this).closest('form'); + var $fields = $form.find('.field'); + if (($(this).val() == "") || $(this).val() == "GPU-Passthrough") { + $form.find('[rel=vgpuType]').hide(); + } else if ($(this).val() == "VGPU") { + $form.find('[rel=vgpuType]').css('display', 'block'); + } + }); + } + }, + + vgpuType: { + label: 'VGPU Type', + select: function(args) { + var items = []; + items.push({ + id: '', + description: '' + }); + items.push({ + id: 'GRID K100', + description: 'GRID K100' + }); + items.push({ + id: 'GRID K140Q', + description: 'GRID K140Q' + }); + items.push({ + id: 'GRID K200', + description: 'GRID K200' + }); + items.push({ + id: 'GRID K240Q', + description: 'GRID K240Q' + }); + items.push({ + id: 'GRID K260Q', + description: 'GRID K260Q' + }); + args.response.success({ + data: items + }); + } + }, + domainId: { label: 'label.domain', docID: 'helpComputeOfferingDomain', @@ -428,6 +493,14 @@ array1.push("&serviceofferingdetails[0].ImplicitDedicationMode" + "=" + args.data.plannerMode); } + if (args.data.pciDevice != "") { + array1.push("&serviceofferingdetails[1].pciDevice" + "=" + args.data.pciDevice); + } + + if (args.data.pciDevice == "VGPU") { + array1.push("&serviceofferingdetails[2].vgpuType" + "=" + args.data.vgpuType); + } + if (args.data.networkRate != null && args.data.networkRate.length > 0) { $.extend(data, { networkrate: args.data.networkRate diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index e5b2e85d34f..10d25917f91 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -1804,7 +1804,9 @@ memory: { label: 'label.memory.mb' }, - + vgpu: { + label: 'VGPU' + }, haenable: { label: 'label.ha.enabled', converter: cloudStack.converters.toBooleanText