From a4d3ec476fce815ef32e8273c7311850b5617f06 Mon Sep 17 00:00:00 2001 From: Marcus Sorensen Date: Mon, 10 Mar 2014 16:11:04 -0600 Subject: [PATCH 01/11] CLOUDSTACK-6220: Take 2, Fix cloudstack init scripts so that they don't use fully qualified path as script name. Fix for commit 9dd57c22b02afcddb1d6c8ddc3e1b578961454e3 --- agent/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-agent.in | 2 +- agent/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-agent.in | 2 +- agent/distro/opensuse/sles/SYSCONFDIR/init.d/cloud-agent.in | 2 +- agent/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-agent.in | 2 +- agent/distro/sles/SYSCONFDIR/init.d/cloud-agent.in | 2 +- packaging/centos63/cloud-agent.rc | 2 +- packaging/centos63/cloud-ipallocator.rc | 2 +- .../distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in | 2 +- .../distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in | 2 +- python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in | 2 +- python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in | 2 +- python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in | 2 +- python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in | 2 +- .../distro/centos/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in | 2 +- .../distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in | 2 +- .../distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in | 2 +- systemvm/distro/ubuntu/SYSCONFDIR/init.d/cloud-console-proxy.in | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/agent/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-agent.in b/agent/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-agent.in index 704ef6b05b4..d1769ccdfb0 100755 --- a/agent/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-agent.in +++ b/agent/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-agent.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/agent/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-agent.in b/agent/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-agent.in index 704ef6b05b4..d1769ccdfb0 100755 --- a/agent/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-agent.in +++ b/agent/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-agent.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/agent/distro/opensuse/sles/SYSCONFDIR/init.d/cloud-agent.in b/agent/distro/opensuse/sles/SYSCONFDIR/init.d/cloud-agent.in index e684888b86e..741317bde43 100644 --- a/agent/distro/opensuse/sles/SYSCONFDIR/init.d/cloud-agent.in +++ b/agent/distro/opensuse/sles/SYSCONFDIR/init.d/cloud-agent.in @@ -33,7 +33,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/agent/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-agent.in b/agent/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-agent.in index 704ef6b05b4..d1769ccdfb0 100644 --- a/agent/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-agent.in +++ b/agent/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-agent.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/agent/distro/sles/SYSCONFDIR/init.d/cloud-agent.in b/agent/distro/sles/SYSCONFDIR/init.d/cloud-agent.in index e684888b86e..741317bde43 100644 --- a/agent/distro/sles/SYSCONFDIR/init.d/cloud-agent.in +++ b/agent/distro/sles/SYSCONFDIR/init.d/cloud-agent.in @@ -33,7 +33,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/packaging/centos63/cloud-agent.rc b/packaging/centos63/cloud-agent.rc index af1c624eda2..ece4c2732f8 100755 --- a/packaging/centos63/cloud-agent.rc +++ b/packaging/centos63/cloud-agent.rc @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=/var/run/"$SHORTNAME".pid LOCKFILE=/var/lock/subsys/"$SHORTNAME" LOGDIR=/var/log/cloudstack/agent diff --git a/packaging/centos63/cloud-ipallocator.rc b/packaging/centos63/cloud-ipallocator.rc index 9d1adb43506..d26287dcb3c 100755 --- a/packaging/centos63/cloud-ipallocator.rc +++ b/packaging/centos63/cloud-ipallocator.rc @@ -25,7 +25,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=/var/run/"$SHORTNAME".pid LOCKFILE=/var/lock/subsys/"$SHORTNAME" LOGFILE=/var/log/cloudstack/ipallocator/ipallocator.log diff --git a/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in index ae8d2157541..3921484e54c 100755 --- a/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in +++ b/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in @@ -25,7 +25,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@IPALOCATORLOG@ diff --git a/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in index 49fd9b6f237..23ec8f3cd66 100755 --- a/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in +++ b/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in @@ -25,7 +25,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in b/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in index 901d5845f39..558f5a2ee98 100755 --- a/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in +++ b/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in @@ -32,7 +32,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in index 49fd9b6f237..23ec8f3cd66 100644 --- a/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in +++ b/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in @@ -25,7 +25,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in b/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in index 901d5845f39..558f5a2ee98 100755 --- a/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in +++ b/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in @@ -32,7 +32,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in b/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in index 5ec6da07f23..e2cb36197a6 100755 --- a/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in +++ b/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@AGENTLOG@ diff --git a/systemvm/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in b/systemvm/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in index e43f6413c67..3ec4d0655cd 100644 --- a/systemvm/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in +++ b/systemvm/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@CPLOG@ diff --git a/systemvm/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in b/systemvm/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in index e43f6413c67..3ec4d0655cd 100644 --- a/systemvm/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in +++ b/systemvm/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@CPLOG@ diff --git a/systemvm/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in b/systemvm/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in index e43f6413c67..3ec4d0655cd 100644 --- a/systemvm/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in +++ b/systemvm/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in @@ -26,7 +26,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@CPLOG@ diff --git a/systemvm/distro/ubuntu/SYSCONFDIR/init.d/cloud-console-proxy.in b/systemvm/distro/ubuntu/SYSCONFDIR/init.d/cloud-console-proxy.in index 87a653e920b..0c7be737353 100755 --- a/systemvm/distro/ubuntu/SYSCONFDIR/init.d/cloud-console-proxy.in +++ b/systemvm/distro/ubuntu/SYSCONFDIR/init.d/cloud-console-proxy.in @@ -27,7 +27,7 @@ # set environment variables -SHORTNAME="basename $0" +SHORTNAME=`basename $0` PIDFILE=@PIDDIR@/"$SHORTNAME".pid LOCKFILE=@LOCKDIR@/"$SHORTNAME" LOGFILE=@CPLOG@ From 466825a167b3ee465a92297b96fb517bb35a810f Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Mon, 10 Mar 2014 15:27:13 -0700 Subject: [PATCH 02/11] Fixed nonoss build --- .../src/com/cloud/network/element/CiscoNexusVSMElement.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/vmware/src/com/cloud/network/element/CiscoNexusVSMElement.java b/plugins/hypervisors/vmware/src/com/cloud/network/element/CiscoNexusVSMElement.java index c33af27d244..0e2a9117f10 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/network/element/CiscoNexusVSMElement.java +++ b/plugins/hypervisors/vmware/src/com/cloud/network/element/CiscoNexusVSMElement.java @@ -79,6 +79,8 @@ public class CiscoNexusVSMElement extends CiscoNexusVSMDeviceManagerImpl impleme ClusterDao _clusterDao; @Inject ClusterVSMMapDao _clusterVSMDao; + @Inject + ManagementService _mgr; @Override public Map> getCapabilities() { @@ -190,7 +192,8 @@ public class CiscoNexusVSMElement extends CiscoNexusVSMDeviceManagerImpl impleme // Else if there is only a zoneId defined, get a list of all vmware clusters // in the zone, and then for each cluster, pull the VSM and prepare a list. if (zoneId != null && zoneId.longValue() != 0) { - ManagementService ref = cmd.getMgmtServiceRef(); + ManagementService ref = _mgr; + ; List clusterList = ref.searchForClusters(zoneId, cmd.getStartIndex(), cmd.getPageSizeVal(), "VMware"); if (clusterList.size() == 0) { From 33a0dec965c96483c8cd55e309250662fca5e2da Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Mon, 10 Mar 2014 16:22:34 -0700 Subject: [PATCH 03/11] CLOUDSTACK-6221: Publish first class objects involved in an operation (for now vm uuid) on the event bus . Example - during attach/detachIso along with iso id, vm id should be available as well. --- api/src/com/cloud/event/EventTypes.java | 1 + server/src/com/cloud/api/ApiDispatcher.java | 9 ++++++++- server/src/com/cloud/api/ApiServer.java | 11 ++++++++++- server/src/com/cloud/event/ActionEventUtils.java | 8 +++++++- utils/src/com/cloud/utils/ReflectUtil.java | 13 +++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 7994adab82e..a5fc0f0f7d3 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -483,6 +483,7 @@ public class EventTypes { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking // current ActionEvent annotation semantics + // TODO #2 - The map should be from event type to class. entityEventDetails = new HashMap(); diff --git a/server/src/com/cloud/api/ApiDispatcher.java b/server/src/com/cloud/api/ApiDispatcher.java index 8f980d99038..27ff9521974 100755 --- a/server/src/com/cloud/api/ApiDispatcher.java +++ b/server/src/com/cloud/api/ApiDispatcher.java @@ -22,6 +22,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; import com.cloud.event.EventTypes; +import com.cloud.utils.ReflectUtil; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseAsyncCreateCmd; @@ -83,13 +85,18 @@ public class ApiDispatcher { final BaseAsyncCmd asyncCmd = (BaseAsyncCmd)cmd; final String startEventId = params.get(ApiConstants.CTX_START_EVENT_ID); - String uuid = params.get("uuid"); + String uuid = params.get(ApiConstants.UUID); ctx.setStartEventId(Long.valueOf(startEventId)); // Fow now use the key from EventTypes.java rather than getInstanceType bcz the later doesn't refer to the interfaces + // Add the resource id in the call context, also add some other first class object ids (for now vm) if available. + // TODO - this should be done for all the uuids passed in the cmd - so should be moved where uuid to id conversion happens. if(EventTypes.getEntityForEvent(asyncCmd.getEventType()) != null){ ctx.putContextParameter(EventTypes.getEntityForEvent(asyncCmd.getEventType()), uuid); } + if(params.get(ApiConstants.VIRTUAL_MACHINE_ID) != null){ + ctx.putContextParameter(ReflectUtil.getEntityName(VirtualMachine.class), params.get(ApiConstants.VIRTUAL_MACHINE_ID)); + } // Synchronise job on the object if needed if (asyncCmd.getJob() != null && asyncCmd.getSyncObjId() != null && asyncCmd.getSyncObjType() != null) { diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index 7ad7c106952..35026897d79 100755 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -54,6 +54,8 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.cloud.event.EventTypes; +import com.cloud.utils.ReflectUtil; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -503,6 +505,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer final CallContext ctx = CallContext.current(); final Long callerUserId = ctx.getCallingUserId(); final Account caller = ctx.getCallingAccount(); + String vmUUID = params.get(ApiConstants.VIRTUAL_MACHINE_ID); // Queue command based on Cmd super class: // BaseCmd: cmd is dispatched to ApiDispatcher, executed, serialized and returned. @@ -519,7 +522,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer params.put("id", objectId.toString()); } else { // Extract the uuid before params are processed and id reflects internal db id - objectUuid = params.get("id"); + objectUuid = params.get(ApiConstants.ID); dispatchChainFactory.getStandardDispatchChain().dispatch(new DispatchTask(cmdObj, params)); } @@ -538,9 +541,15 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer long startEventId = ctx.getStartEventId(); asyncCmd.setStartEventId(startEventId); + // Add the resource id in the call context, also add some other first class object ids (for now vm) if available. + // TODO - this should be done for all the uuids passed in the cmd - so should be moved where uuid to id conversion happens. if(EventTypes.getEntityForEvent(asyncCmd.getEventType()) != null){ ctx.putContextParameter(EventTypes.getEntityForEvent(asyncCmd.getEventType()), objectUuid); } + if(vmUUID != null){ + ctx.putContextParameter(ReflectUtil.getEntityName(VirtualMachine.class), vmUUID); + } + // save the scheduled event final Long eventId = ActionEventUtils.onScheduledActionEvent((callerUserId == null) ? User.UID_SYSTEM : callerUserId, asyncCmd.getEntityOwnerId(), asyncCmd.getEventType(), diff --git a/server/src/com/cloud/event/ActionEventUtils.java b/server/src/com/cloud/event/ActionEventUtils.java index 59546708bce..28e5680b630 100755 --- a/server/src/com/cloud/event/ActionEventUtils.java +++ b/server/src/com/cloud/event/ActionEventUtils.java @@ -25,6 +25,8 @@ import java.util.Map; import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.utils.ReflectUtil; +import com.cloud.vm.VirtualMachine; import org.apache.log4j.Logger; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -186,10 +188,12 @@ public class ActionEventUtils { String entityType = null; String entityUuid = null; CallContext context = CallContext.current(); + String vmEntityName = ReflectUtil.getEntityName(VirtualMachine.class); + String vmuuid = (String) context.getContextParameter(vmEntityName); Class entityKey = getEntityKey(eventType); if (entityKey != null) { - //FIXME - Remove this + //FIXME - Remove this since it should be covered by the else if condition below. entityUuid = (String)context.getContextParameter(entityKey); if (entityUuid != null) entityType = entityKey.getName(); @@ -220,6 +224,8 @@ public class ActionEventUtils { eventDescription.put("status", state.toString()); eventDescription.put("entity", entityType); eventDescription.put("entityuuid", entityUuid); + //Put all the first class entities that are touched during the action. For now atleast put in the vmid. + eventDescription.put(vmEntityName, vmuuid); eventDescription.put("description", description); String eventDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").format(new Date()); diff --git a/utils/src/com/cloud/utils/ReflectUtil.java b/utils/src/com/cloud/utils/ReflectUtil.java index 379f1c0bd27..9c09980e0fc 100755 --- a/utils/src/com/cloud/utils/ReflectUtil.java +++ b/utils/src/com/cloud/utils/ReflectUtil.java @@ -189,4 +189,17 @@ public class ReflectUtil { } + public static String getEntityName(Class clz){ + if(clz == null) + return null; + + String entityName = clz.getName(); + int index = entityName.lastIndexOf("."); + if (index != -1) { + return entityName.substring(index + 1); + }else{ + return entityName; + } + } + } From 540d020aa5c0cd5d3b4657973b1c8a852bdf6d58 Mon Sep 17 00:00:00 2001 From: Rajesh Battala Date: Tue, 11 Mar 2014 13:09:18 +0530 Subject: [PATCH 04/11] CLOUDSTACK-6179 Execute VR commands on Virtual Resource when commands received to Hyper-V --- .../hypervisor/hyperv/guru/HypervGuru.java | 9 +- .../resource/HypervDirectConnectResource.java | 216 +++++++++++++++++- .../config/etc/init.d/cloud-early-config | 8 +- 3 files changed, 222 insertions(+), 11 deletions(-) diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java index 1d9e7f619f9..bf0795df059 100644 --- a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java @@ -53,7 +53,7 @@ public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { @Inject HypervManager _hypervMgr; @Inject NetworkDao _networkDao; @Inject NetworkModel _networkMgr; - + int MaxNicSupported = 8; @Override public final HypervisorType getHypervisorType() { return HypervisorType.Hyperv; @@ -85,7 +85,7 @@ public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { NicTO[] nics = to.getNics(); // reserve extra NICs - NicTO[] expandedNics = new NicTO[nics.length + _hypervMgr.getRouterExtraPublicNics()]; + NicTO[] expandedNics = new NicTO[MaxNicSupported]; int i = 0; int deviceId = -1; for(i = 0; i < nics.length; i++) { @@ -97,8 +97,9 @@ public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { long networkId = publicNicProfile.getNetworkId(); NetworkVO network = _networkDao.findById(networkId); - - for(; i < nics.length + _hypervMgr.getRouterExtraPublicNics(); i++) { + // for Hyperv Hot Nic plug is not supported and it will support upto 8 nics. + // creating the VR with extra nics (actual nics(3) + extra nics) will be 8 + for(; i < MaxNicSupported; i++) { NicTO nicTo = new NicTO(); nicTo.setDeviceId(deviceId++); nicTo.setBroadcastType(publicNicProfile.getBroadcastType()); diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java index 549f06d9ef1..a3ffa758f8e 100644 --- a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java @@ -83,6 +83,7 @@ import com.cloud.agent.api.NetworkUsageCommand; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.PingRoutingCommand; import com.cloud.agent.api.PingTestCommand; +import com.cloud.agent.api.SetupGuestNetworkCommand; import com.cloud.agent.api.StartCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; @@ -98,6 +99,7 @@ import com.cloud.agent.api.routing.DnsMasqConfigCommand; import com.cloud.agent.api.routing.IpAliasTO; import com.cloud.agent.api.routing.IpAssocAnswer; import com.cloud.agent.api.routing.IpAssocCommand; +import com.cloud.agent.api.routing.IpAssocVpcCommand; import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; @@ -105,6 +107,7 @@ import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.SetFirewallRulesAnswer; import com.cloud.agent.api.routing.SetFirewallRulesCommand; import com.cloud.agent.api.routing.SetMonitorServiceCommand; +import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesAnswer; import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; import com.cloud.agent.api.routing.SetSourceNatAnswer; @@ -119,9 +122,12 @@ import com.cloud.agent.api.routing.VpnUsersCfgCommand; import com.cloud.agent.api.to.DhcpTO; import com.cloud.agent.api.to.FirewallRuleTO; import com.cloud.agent.api.to.IpAddressTO; +import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.PortForwardingRuleTO; import com.cloud.agent.api.to.StaticNatRuleTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.resource.virtualnetwork.VirtualRouterDeployer; +import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; import com.cloud.dc.DataCenter.NetworkType; import com.cloud.exception.InternalErrorException; import com.cloud.host.Host.Type; @@ -135,6 +141,7 @@ import com.cloud.network.rules.FirewallRule; import com.cloud.resource.ServerResource; import com.cloud.resource.ServerResourceBase; import com.cloud.serializer.GsonHelper; +import com.cloud.utils.ExecutionResult; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; import com.cloud.utils.net.NetUtils; @@ -148,7 +155,7 @@ import com.cloud.vm.VirtualMachineName; * Implementation of dummy resource to be returned from discoverer. **/ @Local(value = ServerResource.class) -public class HypervDirectConnectResource extends ServerResourceBase implements ServerResource { +public class HypervDirectConnectResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer { public static final int DEFAULT_AGENT_PORT = 8250; public static final String HOST_VM_STATE_REPORT_COMMAND = "org.apache.cloudstack.HostVmStateReportCommand"; private static final Logger s_logger = Logger.getLogger(HypervDirectConnectResource.class.getName()); @@ -178,6 +185,7 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S private static HypervManager s_hypervMgr; @Inject HypervManager _hypervMgr; + protected VirtualRoutingResource _vrResource; @PostConstruct void init() { @@ -421,8 +429,9 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S s_logger.error(errMsg, e); return null; } - - if (clazz == CheckSshCommand.class) { + if (cmd instanceof NetworkElementCommand) { + return _vrResource.executeRequest((NetworkElementCommand)cmd); + }if (clazz == CheckSshCommand.class) { answer = execute((CheckSshCommand)cmd); } else if (clazz == GetDomRVersionCmd.class) { answer = execute((GetDomRVersionCmd)cmd); @@ -499,6 +508,203 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S } return answer; } + + @Override + public ExecutionResult executeInVR(String routerIP, String script, String args) { + Pair result; + + //TODO: Password should be masked, cannot output to log directly + if (s_logger.isDebugEnabled()) { + s_logger.debug("Run command on VR: " + routerIP + ", script: " + script + " with args: " + args); + } + + try { + result = SshHelper.sshExecute(routerIP, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, "/opt/cloud/bin/" + script + " " + args); + } catch (Exception e) { + String msg = "Command failed due to " + e ; + s_logger.error(msg); + result = new Pair(false, msg); + } + if (s_logger.isDebugEnabled()) { + s_logger.debug(script + " execution result: " + result.first().toString()); + } + return new ExecutionResult(result.first(), result.second()); + } + + @Override + public ExecutionResult createFileInVR(String routerIp, String filePath, String fileName, String content) { + File keyFile = getSystemVMKeyFile(); + try { + SshHelper.scpTo(routerIp, 3922, "root", keyFile, null, filePath, content.getBytes(), fileName, null); + } catch (Exception e) { + s_logger.warn("Fail to create file " + filePath + fileName + " in VR " + routerIp, e); + return new ExecutionResult(false, e.getMessage()); + } + return new ExecutionResult(true, null); + } + + @Override + public ExecutionResult prepareCommand(NetworkElementCommand cmd) { + //Update IP used to access router + cmd.setRouterAccessIp(getRouterSshControlIp(cmd)); + assert cmd.getRouterAccessIp() != null; + + if (cmd instanceof IpAssocVpcCommand) { + return prepareNetworkElementCommand((IpAssocVpcCommand)cmd); + } else if (cmd instanceof IpAssocCommand) { + return prepareNetworkElementCommand((IpAssocCommand)cmd); + } else if (cmd instanceof SetSourceNatCommand) { + return prepareNetworkElementCommand((SetSourceNatCommand)cmd); + } else if (cmd instanceof SetupGuestNetworkCommand) { + return prepareNetworkElementCommand((SetupGuestNetworkCommand)cmd); + } else if (cmd instanceof SetNetworkACLCommand) { + return prepareNetworkElementCommand((SetNetworkACLCommand)cmd); + } + return new ExecutionResult(true, null); + } + + private ExecutionResult prepareNetworkElementCommand(IpAssocCommand cmd) { + try { + + IpAddressTO[] ips = cmd.getIpAddresses(); + String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + String controlIp = getRouterSshControlIp(cmd); + + for (IpAddressTO ip : ips) { + /** + * TODO support other networks + */ + URI broadcastUri = BroadcastDomainType.fromString(ip.getBroadcastUri()); + if (BroadcastDomainType.getSchemeValue(broadcastUri) != BroadcastDomainType.Vlan) { + throw new InternalErrorException("Unable to assign a public IP to a VIF on network " + ip.getBroadcastUri()); + } + int vlanId = Integer.parseInt(BroadcastDomainType.getValue(broadcastUri)); + int publicNicInfo = -1; + publicNicInfo = getVmNics(routerName, vlanId); + + boolean addVif = false; + if (ip.isAdd() && publicNicInfo == -1) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Plug new NIC to associate" + controlIp + " to " + ip.getPublicIp()); + } + addVif = true; + } + + if (addVif) { + Pair nicdevice = findRouterFreeEthDeviceIndex(controlIp); + publicNicInfo = nicdevice.first(); + if (publicNicInfo > 0) { + modifyNicVlan(routerName, vlanId, nicdevice.second()); + // After modifying the vnic on VR, check the VR VNics config in the host and get the device position + publicNicInfo = getVmNics(routerName, vlanId); + // As a new nic got activated in the VR. add the entry in the NIC's table. + networkUsage(controlIp, "addVif", "eth" + publicNicInfo); + } + else { + // we didn't find any eth device available in VR to configure the ip range with new VLAN + String msg = "No Nic is available on DomR VIF to associate/disassociate IP with."; + s_logger.error(msg); + throw new InternalErrorException(msg); + } + ip.setNicDevId(publicNicInfo); + ip.setNewNic(addVif); + } else { + ip.setNicDevId(publicNicInfo); + } + } + } catch (Throwable e) { + s_logger.error("Unexpected exception: " + e.toString() + " will shortcut rest of IPAssoc commands", e); + return new ExecutionResult(false, e.toString()); + } + return new ExecutionResult(true, null); + } + + protected ExecutionResult prepareNetworkElementCommand(SetupGuestNetworkCommand cmd) { + NicTO nic = cmd.getNic(); + String routerIp = getRouterSshControlIp(cmd); + String domrName = + cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + + try { + int ethDeviceNum = findRouterEthDeviceIndex(domrName, routerIp, + nic.getMac()); + nic.setDeviceId(ethDeviceNum); + } catch (Exception e) { + String msg = "Prepare SetupGuestNetwork failed due to " + e.toString(); + s_logger.warn(msg, e); + return new ExecutionResult(false, msg); + } + return new ExecutionResult(true, null); + } + + + private ExecutionResult prepareNetworkElementCommand(IpAssocVpcCommand cmd) { + String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + String routerIp = getRouterSshControlIp(cmd); + + try { + IpAddressTO[] ips = cmd.getIpAddresses(); + for (IpAddressTO ip : ips) { + + int ethDeviceNum = findRouterEthDeviceIndex(routerName, routerIp, ip.getVifMacAddress()); + if (ethDeviceNum < 0) { + if (ip.isAdd()) { + throw new InternalErrorException("Failed to find DomR VIF to associate/disassociate IP with."); + } else { + s_logger.debug("VIF to deassociate IP with does not exist, return success"); + continue; + } + } + + ip.setNicDevId(ethDeviceNum); + } + } catch (Exception e) { + s_logger.error("Prepare Ip Assoc failure on applying one ip due to exception: ", e); + return new ExecutionResult(false, e.toString()); + } + + return new ExecutionResult(true, null); + } + + protected ExecutionResult prepareNetworkElementCommand(SetSourceNatCommand cmd) { + String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + String routerIp = getRouterSshControlIp(cmd); + IpAddressTO pubIp = cmd.getIpAddress(); + + try { + int ethDeviceNum = findRouterEthDeviceIndex(routerName, routerIp, pubIp.getVifMacAddress()); + pubIp.setNicDevId(ethDeviceNum); + } catch (Exception e) { + String msg = "Prepare Ip SNAT failure due to " + e.toString(); + s_logger.error(msg, e); + return new ExecutionResult(false, e.toString()); + } + return new ExecutionResult(true, null); + } + + private ExecutionResult prepareNetworkElementCommand(SetNetworkACLCommand cmd) { + NicTO nic = cmd.getNic(); + String routerName = + cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + String routerIp = getRouterSshControlIp(cmd); + + try { + int ethDeviceNum = findRouterEthDeviceIndex(routerName, routerIp, + nic.getMac()); + nic.setDeviceId(ethDeviceNum); + } catch (Exception e) { + String msg = "Prepare SetNetworkACL failed due to " + e.toString(); + s_logger.error(msg, e); + return new ExecutionResult(false, msg); + } + return new ExecutionResult(true, null); + } + + @Override + public ExecutionResult cleanupCommand(NetworkElementCommand cmd) { + return new ExecutionResult(true, null); + } + protected Answer execute(final RemoteAccessVpnCfgCommand cmd) { String controlIp = getRouterSshControlIp(cmd); StringBuffer argsBuf = new StringBuffer(); @@ -1926,6 +2132,10 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S _configureCalled = true; } + _vrResource = new VirtualRoutingResource(this); + if (!_vrResource.configure(name, new HashMap())) { + throw new ConfigurationException("Unable to configure VirtualRoutingResource"); + } return true; } diff --git a/systemvm/patches/debian/config/etc/init.d/cloud-early-config b/systemvm/patches/debian/config/etc/init.d/cloud-early-config index fa95fdadb16..c287d43452b 100755 --- a/systemvm/patches/debian/config/etc/init.d/cloud-early-config +++ b/systemvm/patches/debian/config/etc/init.d/cloud-early-config @@ -1221,10 +1221,6 @@ start() { patch_log4j parse_cmd_line change_password - if [ "$hyp" == "hyperv" ]; then - # eject the systemvm.iso - eject - fi case $TYPE in router) [ "$NAME" == "" ] && NAME=router @@ -1259,6 +1255,10 @@ start() { setup_default; ;; esac + if [ "$hyp" == "hyperv" ]; then + # eject the systemvm.iso + eject + fi return 0 } From c7d31fe288b13ff4f0f046f83f9eb4a9c2bf06c5 Mon Sep 17 00:00:00 2001 From: Sanjay Tripathi Date: Thu, 6 Feb 2014 15:08:53 +0530 Subject: [PATCH 05/11] CLOUDSTACK-4760 : Enabling GPU support for XenServer. CLOUDSTACK-4762 : Enabling VGPU support for XenServer. This feature is to enable the GPU-passthrough and vGPU functionality, with the help of this feature, admins/users will be able to leverage the GPU graphics unit power by deploying a virtul machine with GPU or vGPU support or by changing the service offering of an existing VM at any later point of time. There GPU/vGPU enabled VMs are able to run graphical applications. For now, this feature is only supported with XenServer hypervisor but can be extended to add the support of other hypervisors. --- .../com/cloud/agent/api/to/GPUDeviceTO.java | 56 +++++ .../cloud/agent/api/to/VirtualMachineTO.java | 9 + api/src/com/cloud/gpu/GPU.java | 49 ++++ .../apache/cloudstack/api/ApiConstants.java | 5 + .../offering/CreateServiceOfferingCmd.java | 18 +- .../cloudstack/api/response/GpuResponse.java | 46 ++++ .../cloudstack/api/response/HostResponse.java | 8 + .../api/response/UserVmResponse.java | 10 + .../cloudstack/api/response/VgpuResponse.java | 52 ++++ .../cloud/agent/api/GetGPUStatsAnswer.java | 36 +++ .../cloud/agent/api/GetGPUStatsCommand.java | 47 ++++ .../agent/api/StartupRoutingCommand.java | 9 + core/src/com/cloud/agent/api/StopCommand.java | 9 + .../com/cloud/resource/ResourceManager.java | 41 ++++ .../cloud/vm/VirtualMachineManagerImpl.java | 18 ++ ...spring-engine-schema-core-daos-context.xml | 2 + .../src/com/cloud/gpu/HostGpuGroupsVO.java | 70 ++++++ .../schema/src/com/cloud/gpu/VGPUTypesVO.java | 82 +++++++ .../com/cloud/gpu/dao/HostGpuGroupsDao.java | 60 +++++ .../cloud/gpu/dao/HostGpuGroupsDaoImpl.java | 94 ++++++++ .../src/com/cloud/gpu/dao/VGPUTypesDao.java | 48 ++++ .../com/cloud/gpu/dao/VGPUTypesDaoImpl.java | 95 ++++++++ engine/schema/src/com/cloud/host/HostVO.java | 13 + .../src/com/cloud/host/dao/HostDaoImpl.java | 18 ++ .../service/ServiceOfferingDetailsVO.java | 4 + .../xen/resource/CitrixResourceBase.java | 108 +++++++++ .../allocator/impl/FirstFitAllocator.java | 17 +- server/src/com/cloud/api/ApiDBUtils.java | 30 +++ .../cloud/api/query/dao/HostJoinDaoImpl.java | 25 ++ .../api/query/dao/UserVmJoinDaoImpl.java | 6 + .../ConfigurationManagerImpl.java | 54 ++++- .../deploy/DeploymentPlanningManagerImpl.java | 14 +- .../cloud/hypervisor/HypervisorGuruBase.java | 15 ++ .../network/NetworkUsageManagerImpl.java | 4 + .../cloud/resource/ResourceManagerImpl.java | 97 +++++++- .../cloud/server/ManagementServerImpl.java | 12 + .../src/com/cloud/server/StatsCollector.java | 21 ++ .../cloud/storage/VolumeApiServiceImpl.java | 8 + .../src/com/cloud/vm/UserVmManagerImpl.java | 10 + .../resource/MockResourceManagerImpl.java | 31 +++ .../vm/DeploymentPlanningManagerImplTest.java | 12 + .../test/resources/createNetworkOffering.xml | 4 +- setup/db/db/schema-430to440.sql | 18 ++ .../smoke/test_deploy_vgpu_enabled_vm.py | 227 ++++++++++++++++++ tools/marvin/marvin/integration/lib/base.py | 3 + ui/scripts/configuration.js | 73 ++++++ ui/scripts/instances.js | 4 +- 47 files changed, 1671 insertions(+), 21 deletions(-) create mode 100644 api/src/com/cloud/agent/api/to/GPUDeviceTO.java create mode 100644 api/src/com/cloud/gpu/GPU.java create mode 100644 api/src/org/apache/cloudstack/api/response/GpuResponse.java create mode 100644 api/src/org/apache/cloudstack/api/response/VgpuResponse.java create mode 100644 core/src/com/cloud/agent/api/GetGPUStatsAnswer.java create mode 100644 core/src/com/cloud/agent/api/GetGPUStatsCommand.java create mode 100644 engine/schema/src/com/cloud/gpu/HostGpuGroupsVO.java create mode 100644 engine/schema/src/com/cloud/gpu/VGPUTypesVO.java create mode 100644 engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDao.java create mode 100644 engine/schema/src/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java create mode 100644 engine/schema/src/com/cloud/gpu/dao/VGPUTypesDao.java create mode 100644 engine/schema/src/com/cloud/gpu/dao/VGPUTypesDaoImpl.java create mode 100644 test/integration/smoke/test_deploy_vgpu_enabled_vm.py 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 From 991e1eb6b119a62dedbc0e39a612941af9999103 Mon Sep 17 00:00:00 2001 From: SrikanteswaraRao Talluri Date: Tue, 11 Mar 2014 16:07:28 +0530 Subject: [PATCH 06/11] CLOUDSTACK-6222: Fix the marvin config generator scripts to include new syntax changes in marvin code and fix the logFolderPath --- tools/marvin/marvin/configGenerator.py | 5 ++-- .../marvin/sandbox/advanced/advanced_env.py | 23 ++++++++----------- .../sandbox/advancedsg/advancedsg_env.py | 18 +++++---------- .../marvin/marvin/sandbox/basic/basic_env.py | 16 ++++--------- .../sandbox/demo/simulator/simulator_setup.py | 16 ++++--------- 5 files changed, 27 insertions(+), 51 deletions(-) diff --git a/tools/marvin/marvin/configGenerator.py b/tools/marvin/marvin/configGenerator.py index 0d79e8ea6ad..0b4a0a132bc 100644 --- a/tools/marvin/marvin/configGenerator.py +++ b/tools/marvin/marvin/configGenerator.py @@ -51,8 +51,7 @@ class configuration(object): class logger(object): def __init__(self): '''TestCase/TestClient''' - self.name = None - self.file = None + self.logFolderPath = None class cloudstackConfiguration(object): @@ -61,7 +60,7 @@ class cloudstackConfiguration(object): self.mgtSvr = [] self.dbSvr = None self.globalConfig = [] - self.logger = [] + self.logger = None class zone(object): diff --git a/tools/marvin/marvin/sandbox/advanced/advanced_env.py b/tools/marvin/marvin/sandbox/advanced/advanced_env.py index 1728e61fb19..f3d30643f21 100644 --- a/tools/marvin/marvin/sandbox/advanced/advanced_env.py +++ b/tools/marvin/marvin/sandbox/advanced/advanced_env.py @@ -54,22 +54,22 @@ def describeResources(config): lbprovider = provider() lbprovider.name = 'InternalLbVm' - pn = physical_network() + pn = physicalNetwork() pn.name = "Sandbox-pnet" pn.vlan = config.get('cloudstack', 'pnet.vlan') pn.tags = ["cloud-simulator-public"] - pn.traffictypes = [traffictype("Guest"), - traffictype("Management", {"simulator" : "cloud-simulator-mgmt"}), - traffictype("Public", {"simulator":"cloud-simulator-public"})] + pn.traffictypes = [trafficType("Guest"), + trafficType("Management", {"simulator" : "cloud-simulator-mgmt"}), + trafficType("Public", {"simulator":"cloud-simulator-public"})] pn.isolationmethods = ["VLAN"] pn.providers.append(vpcprovider) pn.providers.append(lbprovider) - pn2 = physical_network() + pn2 = physicalNetwork() pn2.name = "Sandbox-pnet2" pn2.vlan = config.get('cloudstack', 'pnet2.vlan') pn2.tags = ["cloud-simulator-guest"] - pn2.traffictypes = [traffictype('Guest', {'simulator': 'cloud-simulator-guest'})] + pn2.traffictypes = [trafficType('Guest', {'simulator': 'cloud-simulator-guest'})] pn2.isolationmethods = ["VLAN"] pn2.providers.append(vpcprovider) pn2.providers.append(lbprovider) @@ -137,16 +137,11 @@ def describeResources(config): [zs.globalConfig.append(cfg) for cfg in getGlobalSettings(config)] ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = 'TestClient' - testClientLogger.file = 'testclient.log' + testLogger = logger() + testLogger.logFolderPath = '/tmp/' + zs.logger = testLogger - testCaseLogger = logger() - testCaseLogger.name = 'TestCase' - testCaseLogger.file = 'testcase.log' - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) return zs diff --git a/tools/marvin/marvin/sandbox/advancedsg/advancedsg_env.py b/tools/marvin/marvin/sandbox/advancedsg/advancedsg_env.py index 9cf4a0aa98f..284ea2eb25b 100644 --- a/tools/marvin/marvin/sandbox/advancedsg/advancedsg_env.py +++ b/tools/marvin/marvin/sandbox/advancedsg/advancedsg_env.py @@ -51,11 +51,11 @@ def describeResources(config): sgprovider.broadcastdomainrange = 'ZONE' sgprovider.name = 'SecurityGroupProvider' - pn = physical_network() + pn = physicalNetwork() pn.name = "Sandbox-pnet" pn.tags = ["cloud-simulator-pnet"] - pn.traffictypes = [traffictype("Guest"), - traffictype("Management", {"simulator" : "cloud-simulator-mgmt"})] + pn.traffictypes = [trafficType("Guest"), + trafficType("Management", {"simulator" : "cloud-simulator-mgmt"})] pn.isolationmethods = ["VLAN"] pn.providers.append(sgprovider) @@ -121,16 +121,10 @@ def describeResources(config): [zs.globalConfig.append(cfg) for cfg in getGlobalSettings(config)] ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = 'TestClient' - testClientLogger.file = 'testclient.log' + testLogger = logger() + testLogger.logFolderPath = '/tmp/' + zs.logger = testLogger - testCaseLogger = logger() - testCaseLogger.name = 'TestCase' - testCaseLogger.file = 'testcase.log' - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) return zs diff --git a/tools/marvin/marvin/sandbox/basic/basic_env.py b/tools/marvin/marvin/sandbox/basic/basic_env.py index bf106fcb8de..6198163a866 100644 --- a/tools/marvin/marvin/sandbox/basic/basic_env.py +++ b/tools/marvin/marvin/sandbox/basic/basic_env.py @@ -52,9 +52,9 @@ def describeResources(config): sgprovider.broadcastdomainrange = 'Pod' sgprovider.name = 'SecurityGroupProvider' - pn = physical_network() + pn = physicalNetwork() pn.name = "Sandbox-pnet" - pn.traffictypes = [traffictype("Guest"), traffictype("Management")] + pn.traffictypes = [trafficType("Guest"), trafficType("Management")] pn.isolationmethods = ["L3"] pn.providers.append(sgprovider) @@ -119,16 +119,10 @@ def describeResources(config): [zs.globalConfig.append(cfg) for cfg in getGlobalSettings(config)] ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = 'TestClient' - testClientLogger.file = '/var/log/testclient.log' + testLogger = logger() + testLogger.logFolderPath = '/tmp/' + zs.logger = testLogger - testCaseLogger = logger() - testCaseLogger.name = 'TestCase' - testCaseLogger.file = '/var/log/testcase.log' - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) return zs diff --git a/tools/marvin/marvin/sandbox/demo/simulator/simulator_setup.py b/tools/marvin/marvin/sandbox/demo/simulator/simulator_setup.py index d45d48243bd..08b20cce67b 100644 --- a/tools/marvin/marvin/sandbox/demo/simulator/simulator_setup.py +++ b/tools/marvin/marvin/sandbox/demo/simulator/simulator_setup.py @@ -46,9 +46,9 @@ def describeResources(config): vpcprovider = provider() vpcprovider.name = 'VpcVirtualRouter' - pn = physical_network() + pn = physicalNetwork() pn.name = "Sandbox-pnet" - pn.traffictypes = [traffictype("Guest"), traffictype("Management"), traffictype("Public")] + pn.traffictypes = [trafficType("Guest"), trafficType("Management"), trafficType("Public")] pn.isolationmethods = ["VLAN"] pn.providers.append(vpcprovider) pn.vlan = config.get('cloudstack', 'zone.vlan') @@ -120,16 +120,10 @@ def describeResources(config): [zs.globalConfig.append(cfg) for cfg in getGlobalSettings(config)] ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = 'TestClient' - testClientLogger.file = '/var/log/testclient.log' + testLogger = logger() + testLogger.logFolderPath = '/tmp/' + zs.logger = testLogger - testCaseLogger = logger() - testCaseLogger.name = 'TestCase' - testCaseLogger.file = '/var/log/testcase.log' - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) return zs From c874e20c24e8bcf7b606c55ef924606b857c1f6d Mon Sep 17 00:00:00 2001 From: Marcus Sorensen Date: Tue, 11 Mar 2014 11:13:12 -0600 Subject: [PATCH 07/11] CLOUDSTACK-6225: Check libvirt version and volume format before adding flag VIR_STORAGE_VOL_RESIZE_ALLOCATE to resize volume libvirt call --- .../hypervisor/kvm/resource/LibvirtComputingResource.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 5912dd34d2a..f4f6c740c1d 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -1804,8 +1804,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv try { Connect conn = LibvirtConnection.getConnection(); StorageVol v = conn.storageVolLookupByPath(path); + int flags = 0; - int flags = 1; + if (conn.getLibVirVersion() > 1001000 && vol.getFormat() == PhysicalDiskFormat.RAW) { + flags = 1; + } if (shrinkOk) { flags = 4; } From 415e4bffd652d0a92b36cc6ff1bec0e3ea0462da Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Tue, 11 Mar 2014 11:18:49 -0700 Subject: [PATCH 08/11] CLOUDSTACK-6226: UI > multi widget > dropdown field > translate option value. --- ui/scripts/ui/widgets/multiEdit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scripts/ui/widgets/multiEdit.js b/ui/scripts/ui/widgets/multiEdit.js index 873775d7221..713ebdeffd3 100755 --- a/ui/scripts/ui/widgets/multiEdit.js +++ b/ui/scripts/ui/widgets/multiEdit.js @@ -927,7 +927,7 @@ response: { success: function(args) { $(args.data).each(function() { - $('