diff --git a/api/src/com/cloud/agent/api/to/DatadiskTO.java b/api/src/com/cloud/agent/api/to/DatadiskTO.java new file mode 100644 index 000000000000..514ca98f467a --- /dev/null +++ b/api/src/com/cloud/agent/api/to/DatadiskTO.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.agent.api.to; + +public class DatadiskTO { + private String path; + private long virtualSize; + private long fileSize; + boolean bootable; + + public DatadiskTO() { + } + + public DatadiskTO(String path, long virtualSize, long fileSize, boolean bootable) { + this.path = path; + this.virtualSize = virtualSize; + this.fileSize = fileSize; + this.bootable = bootable; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getVirtualSize() { + return virtualSize; + } + + public void setVirtualSize(Long virtualSize) { + this.virtualSize = virtualSize; + } + + public Long getFileSize() { + return fileSize; + } + + public boolean isBootable() { + return bootable; + } +} diff --git a/api/src/com/cloud/storage/Storage.java b/api/src/com/cloud/storage/Storage.java index cc8d0100f361..69aadc9b8951 100755 --- a/api/src/com/cloud/storage/Storage.java +++ b/api/src/com/cloud/storage/Storage.java @@ -112,7 +112,8 @@ public static enum TemplateType { SYSTEM, /* routing, system vm template */ BUILTIN, /* buildin template */ PERHOST, /* every host has this template, don't need to install it in secondary storage */ - USER /* User supplied template/iso */ + USER, /* User supplied template/iso */ + DATADISK /* Template corresponding to a datadisk(non root disk) present in an OVA */ } public static enum StoragePoolType { diff --git a/api/src/com/cloud/template/VirtualMachineTemplate.java b/api/src/com/cloud/template/VirtualMachineTemplate.java index 599212bb039f..832cd6c61d4e 100755 --- a/api/src/com/cloud/template/VirtualMachineTemplate.java +++ b/api/src/com/cloud/template/VirtualMachineTemplate.java @@ -100,4 +100,6 @@ public enum TemplateFilter { Map getDetails(); boolean isDynamicallyScalable(); + + Long getParentTemplateId(); } diff --git a/api/src/com/cloud/vm/DiskProfile.java b/api/src/com/cloud/vm/DiskProfile.java index a37f7aaf57b6..d9097748363b 100644 --- a/api/src/com/cloud/vm/DiskProfile.java +++ b/api/src/com/cloud/vm/DiskProfile.java @@ -139,6 +139,10 @@ public Long getTemplateId() { return templateId; } + public void setTemplateId(Long templateId) { + this.templateId = templateId; + } + /** * @return disk offering id that the disk is based on. */ diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index af4e1d3699e2..c4bca7138ed6 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -50,6 +50,7 @@ import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network.IpAddresses; +import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; import com.cloud.storage.StoragePool; import com.cloud.template.VirtualMachineTemplate; @@ -185,6 +186,11 @@ UserVm startVirtualMachine(StartVMCmd cmd) throws StorageUnavailableException, E * @param memory * @param cpuNumber * @param customId + * @param dataDiskTemplateToDiskOfferingMap + * - Datadisk template to Disk offering Map + * an optional parameter that creates additional data disks for the virtual machine + * For each of the templates in the map, a data disk will be created from the corresponding + * disk offering obtained from the map * @return UserVm object if successful. * * @throws InsufficientCapacityException @@ -199,7 +205,7 @@ UserVm startVirtualMachine(StartVMCmd cmd) throws StorageUnavailableException, E UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, - List affinityGroupIdList, Map customParameter, String customId) throws InsufficientCapacityException, + List affinityGroupIdList, Map customParameter, String customId, Map dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -258,6 +264,11 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s * @param memory * @param cpuNumber * @param customId + * @param dataDiskTemplateToDiskOfferingMap + * - Datadisk template to Disk offering Map + * an optional parameter that creates additional data disks for the virtual machine + * For each of the templates in the map, a data disk will be created from the corresponding + * disk offering obtained from the map * @return UserVm object if successful. * * @throws InsufficientCapacityException @@ -272,7 +283,7 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, - List affinityGroupIdList, Map customParameters, String customId) throws InsufficientCapacityException, + List affinityGroupIdList, Map customParameters, String customId, Map dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -329,6 +340,11 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin * @param memory * @param cpuNumber * @param customId + * @param dataDiskTemplateToDiskOfferingMap + * - Datadisk template to Disk offering Map + * an optional parameter that creates additional data disks for the virtual machine + * For each of the templates in the map, a data disk will be created from the corresponding + * disk offering obtained from the map * @return UserVm object if successful. * * @throws InsufficientCapacityException @@ -343,7 +359,7 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, - Map customParameters, String customId) + Map customParameters, String customId, Map dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index a3faca7bd0ac..eb4ab12d7364 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -67,6 +67,7 @@ public class ApiConstants { public static final String MIN_IOPS = "miniops"; public static final String MAX_IOPS = "maxiops"; public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve"; + public static final String DATADISKTEMPLATE_TO_DISKOFFERING_LIST = "datadisktemplatetodiskofferinglist"; public static final String DESCRIPTION = "description"; public static final String DESTINATION_ZONE_ID = "destzoneid"; public static final String DETAILS = "details"; @@ -176,6 +177,7 @@ public class ApiConstants { public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor"; public static final String PARAMS = "params"; public static final String PARENT_DOMAIN_ID = "parentdomainid"; + public static final String PARENT_TEMPLATE_ID = "parenttemplateid"; public static final String PASSWORD = "password"; public static final String NEW_PASSWORD = "new_password"; public static final String PASSWORD_ENABLED = "passwordenabled"; diff --git a/api/src/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/org/apache/cloudstack/api/ResponseGenerator.java index 10fb6df3a18c..3cb656d8a9e5 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -281,6 +281,8 @@ public interface ResponseGenerator { Host findHostById(Long hostId); + DiskOffering findDiskOfferingById(Long diskOfferingId); + VpnUsersResponse createVpnUserResponse(VpnUser user); RemoteAccessVpnResponse createRemoteAccessVpnResponse(RemoteAccessVpn vpn); diff --git a/api/src/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index 7a2a15834ac9..1982ea058b87 100644 --- a/api/src/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -71,6 +71,9 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd { @Parameter(name=ApiConstants.SHOW_REMOVED, type=CommandType.BOOLEAN, description="show removed templates as well") private Boolean showRemoved; + @Parameter(name = ApiConstants.PARENT_TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "list datadisk templates by parent template id", + since = "4.5") + private Long parentTemplateId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -99,6 +102,10 @@ public Boolean getShowRemoved() { return (showRemoved != null ? showRemoved : false); } + public Long getParentTemplateId() { + return parentTemplateId; + } + public boolean listInReadyState() { Account account = CallContext.current().getCallingAccount(); diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 0adc57be646f..8c7f6f2cd087 100755 --- a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -190,6 +190,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd { @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for vm allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin }) private String deploymentPlanner; + @Parameter(name = ApiConstants.DATADISKTEMPLATE_TO_DISKOFFERING_LIST, type = CommandType.MAP, since = "4.5", description = "datadisk template to disk-offering mapping;" + + " an optional parameter used to create additional data disks from datadisk templates; can't be specified with diskOfferingId parameter") + private Map dataDiskTemplateToDiskOfferingList; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -393,6 +397,37 @@ public List getAffinityGroupIdList() { } } + public Map getDataDiskTemplateToDiskOfferingMap() { + if (diskOfferingId != null && dataDiskTemplateToDiskOfferingList != null) { + throw new InvalidParameterValueException("diskofferingid paramter can't be specified along with datadisktemplatetodiskofferinglist parameter"); + } + HashMap dataDiskTemplateToDiskOfferingMap = new HashMap(); + if (dataDiskTemplateToDiskOfferingList != null && !dataDiskTemplateToDiskOfferingList.isEmpty()) { + Collection dataDiskTemplatesCollection = dataDiskTemplateToDiskOfferingList.values(); + Iterator iter = dataDiskTemplatesCollection.iterator(); + while (iter.hasNext()) { + HashMap dataDiskTemplates = (HashMap)iter.next(); + Long dataDiskTemplateId; + DiskOffering dataDiskOffering = null; + VirtualMachineTemplate dataDiskTemplate= _entityMgr.findByUuid(VirtualMachineTemplate.class, dataDiskTemplates.get("datadisktemplateid")); + if (dataDiskTemplate == null) { + dataDiskTemplate = _entityMgr.findById(VirtualMachineTemplate.class, dataDiskTemplates.get("datadisktemplateid")); + if (dataDiskTemplate == null) + throw new InvalidParameterValueException("Unable to translate and find entity with datadisktemplateid " + dataDiskTemplates.get("datadisktemplateid")); + } + dataDiskTemplateId = dataDiskTemplate.getId(); + dataDiskOffering = _entityMgr.findByUuid(DiskOffering.class, dataDiskTemplates.get("diskofferingid")); + if (dataDiskOffering == null) { + dataDiskOffering = _entityMgr.findById(DiskOffering.class, dataDiskTemplates.get("diskofferingid")); + if (dataDiskOffering == null) + throw new InvalidParameterValueException("Unable to translate and find entity with diskofferingId " + dataDiskTemplates.get("diskofferingid")); + } + dataDiskTemplateToDiskOfferingMap.put(dataDiskTemplateId, dataDiskOffering); + } + } + return dataDiskTemplateToDiskOfferingMap; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -585,13 +620,13 @@ public void create() throws ResourceAllocationException { } else { vm = _userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(), owner, name, displayName, diskOfferingId, size, group, getHypervisor(), getHttpMethod(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, displayVm, keyboard, getAffinityGroupIdList(), - getDetails(), getCustomId()); + getDetails(), getCustomId(), getDataDiskTemplateToDiskOfferingMap()); } } else { if (zone.isSecurityGroupEnabled()) { vm = _userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, getNetworkIds(), getSecurityGroupIdList(), owner, name, displayName, diskOfferingId, size, group, getHypervisor(), getHttpMethod(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, displayVm, keyboard, - getAffinityGroupIdList(), getDetails(), getCustomId()); + getAffinityGroupIdList(), getDetails(), getCustomId(), getDataDiskTemplateToDiskOfferingMap()); } else { if (getSecurityGroupIdList() != null && !getSecurityGroupIdList().isEmpty()) { @@ -599,7 +634,7 @@ displayName, diskOfferingId, size, group, getHypervisor(), getHttpMethod(), user } vm = _userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, getNetworkIds(), owner, name, displayName, diskOfferingId, size, group, getHypervisor(), getHttpMethod(), userData, sshKeyPairName, getIpToNetworkMap(), addrs, displayVm, keyboard, getAffinityGroupIdList(), getDetails(), - getCustomId()); + getCustomId(), getDataDiskTemplateToDiskOfferingMap()); } } diff --git a/api/src/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/org/apache/cloudstack/api/response/TemplateResponse.java index 3e21043e3391..6177513d8124 100644 --- a/api/src/org/apache/cloudstack/api/response/TemplateResponse.java +++ b/api/src/org/apache/cloudstack/api/response/TemplateResponse.java @@ -187,6 +187,10 @@ public class TemplateResponse extends BaseResponse implements ControlledViewEnti @Param(description = "true if template contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory") private Boolean isDynamicallyScalable; + @SerializedName("parenttemplateid") + @Param(description = "if Datadisk template, then id of the root disk template this template belongs to") + private String parentTemplateId; + public TemplateResponse() { // zones = new LinkedHashSet(); tags = new LinkedHashSet(); @@ -361,4 +365,8 @@ public void setDynamicallyScalable(boolean isDynamicallyScalable) { public String getZoneId() { return zoneId; } + + public void setParentTemplateId(String parentTemplateId) { + this.parentTemplateId = parentTemplateId; + } } diff --git a/core/src/com/cloud/agent/api/storage/CreateDatadiskTemplateAnswer.java b/core/src/com/cloud/agent/api/storage/CreateDatadiskTemplateAnswer.java new file mode 100644 index 000000000000..58e83356fcf4 --- /dev/null +++ b/core/src/com/cloud/agent/api/storage/CreateDatadiskTemplateAnswer.java @@ -0,0 +1,38 @@ +// 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.storage; + +import org.apache.cloudstack.storage.to.TemplateObjectTO; + +import com.cloud.agent.api.Answer; + +public class CreateDatadiskTemplateAnswer extends Answer { + private TemplateObjectTO dataDiskTemplate = null; + + public CreateDatadiskTemplateAnswer(TemplateObjectTO dataDiskTemplate) { + super(null); + this.dataDiskTemplate = dataDiskTemplate; + } + + public TemplateObjectTO getDataDiskTemplate() { + return dataDiskTemplate; + } + + public CreateDatadiskTemplateAnswer(String errMsg) { + super(null, false, errMsg); + } +} diff --git a/core/src/com/cloud/agent/api/storage/CreateDatadiskTemplateCommand.java b/core/src/com/cloud/agent/api/storage/CreateDatadiskTemplateCommand.java new file mode 100644 index 000000000000..548cc4559fa4 --- /dev/null +++ b/core/src/com/cloud/agent/api/storage/CreateDatadiskTemplateCommand.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.agent.api.storage; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.to.DataTO; + +public final class CreateDatadiskTemplateCommand extends Command { + private DataTO dataDiskTemplate; + private String path; + private long fileSize; + private boolean bootable; + + public CreateDatadiskTemplateCommand(DataTO dataDiskTemplate, String path, long fileSize, boolean bootable) { + super(); + this.dataDiskTemplate = dataDiskTemplate; + this.path = path; + this.fileSize = fileSize; + this.bootable = bootable; + } + + protected CreateDatadiskTemplateCommand() { + super(); + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DataTO getDataDiskTemplate() { + return dataDiskTemplate; + } + + public String getPath() { + return path; + } + + public long getFileSize() { + return fileSize; + } + + public boolean getBootable() { + return bootable; + } +} diff --git a/core/src/com/cloud/agent/api/storage/GetDatadisksAnswer.java b/core/src/com/cloud/agent/api/storage/GetDatadisksAnswer.java new file mode 100644 index 000000000000..58922175c551 --- /dev/null +++ b/core/src/com/cloud/agent/api/storage/GetDatadisksAnswer.java @@ -0,0 +1,40 @@ +// 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.storage; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DatadiskTO; + +public class GetDatadisksAnswer extends Answer { + List dataDiskDetails = new ArrayList(); + + public GetDatadisksAnswer(List dataDiskDetails) { + super(null); + this.dataDiskDetails = dataDiskDetails; + } + + public List getDataDiskDetails() { + return dataDiskDetails; + } + + public GetDatadisksAnswer(String errMsg) { + super(null, false, errMsg); + } +} \ No newline at end of file diff --git a/core/src/com/cloud/agent/api/storage/GetDatadisksCommand.java b/core/src/com/cloud/agent/api/storage/GetDatadisksCommand.java new file mode 100644 index 000000000000..ce0fb1c66d53 --- /dev/null +++ b/core/src/com/cloud/agent/api/storage/GetDatadisksCommand.java @@ -0,0 +1,43 @@ +// 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.storage; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.to.DataTO; + +public final class GetDatadisksCommand extends Command { + private DataTO data; + + public GetDatadisksCommand(DataTO data) { + super(); + this.data = data; + } + + protected GetDatadisksCommand() { + super(); + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DataTO getData() { + return data; + } + +} diff --git a/core/src/com/cloud/storage/template/OVAProcessor.java b/core/src/com/cloud/storage/template/OVAProcessor.java index 0db3bb00e0ac..7f8b7f928dbc 100644 --- a/core/src/com/cloud/storage/template/OVAProcessor.java +++ b/core/src/com/cloud/storage/template/OVAProcessor.java @@ -26,10 +26,12 @@ import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NodeList; import com.cloud.exception.InternalErrorException; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageLayer; +import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.script.Script; @@ -131,6 +133,52 @@ public long getTemplateVirtualSize(String templatePath, String templateName) thr } } + public Pair getDiskDetails(String ovfFilePath, String diskName) throws InternalErrorException { + long virtualSize = 0; + long fileSize = 0; + String fileId = null; + try { + Document ovfDoc = null; + ovfDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(ovfFilePath)); + NodeList disks = ovfDoc.getElementsByTagName("Disk"); + NodeList files = ovfDoc.getElementsByTagName("File"); + for (int j = 0; j < files.getLength(); j++) { + Element file = (Element)files.item(j); + if (file.getAttribute("ovf:href").equals(diskName)) { + fileSize = Long.parseLong(file.getAttribute("ovf:size")); + fileId = file.getAttribute("ovf:id"); + break; + } + } + for (int i = 0; i < disks.getLength(); i++) { + Element disk = (Element)disks.item(i); + if (disk.getAttribute("ovf:fileRef").equals(fileId)) { + virtualSize = Long.parseLong(disk.getAttribute("ovf:capacity")); + String allocationUnits = disk.getAttribute("ovf:capacityAllocationUnits"); + if ((virtualSize != 0) && (allocationUnits != null)) { + long units = 1; + if (allocationUnits.equalsIgnoreCase("KB") || allocationUnits.equalsIgnoreCase("KiloBytes") || allocationUnits.equalsIgnoreCase("byte * 2^10")) { + units = 1024; + } else if (allocationUnits.equalsIgnoreCase("MB") || allocationUnits.equalsIgnoreCase("MegaBytes") || allocationUnits.equalsIgnoreCase("byte * 2^20")) { + units = 1024 * 1024; + } else if (allocationUnits.equalsIgnoreCase("GB") || allocationUnits.equalsIgnoreCase("GigaBytes") || allocationUnits.equalsIgnoreCase("byte * 2^30")) { + units = 1024 * 1024 * 1024; + } + virtualSize = virtualSize * units; + } else { + throw new InternalErrorException("Failed to read capacity and capacityAllocationUnits from the OVF file: " + ovfFilePath); + } + break; + } + } + return new Pair(virtualSize, fileSize); + } catch (Exception e) { + String msg = "Unable to parse OVF XML document to get the virtual disk size due to" + e; + s_logger.error(msg); + throw new InternalErrorException(msg); + } + } + private String getOVFFilePath(String srcOVAFileName) { File file = new File(srcOVAFileName); assert (_storage != null); diff --git a/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java b/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java index b201c386f497..55dbcc53f084 100644 --- a/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java +++ b/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java @@ -41,6 +41,8 @@ public class TemplateObjectTO implements DataTO { private Long size; private Long physicalSize; private Hypervisor.HypervisorType hypervisorType; + private boolean bootable; + private String uniqueName; public TemplateObjectTO() { @@ -70,6 +72,9 @@ public TemplateObjectTO(TemplateInfo template) { this.accountId = template.getAccountId(); this.name = template.getUniqueName(); this.format = template.getFormat(); + this.uniqueName = template.getUniqueName(); + this.size = template.getSize(); + if (template.getDataStore() != null) { this.imageDataStore = template.getDataStore().getTO(); } @@ -204,10 +209,26 @@ public Long getPhysicalSize() { return physicalSize; } + public void setIsBootable(boolean bootable) { + this.bootable = bootable; + } + + public boolean isBootable() { + return bootable; + } + public void setPhysicalSize(Long physicalSize) { this.physicalSize = physicalSize; } + public String getUniqueName() { + return this.uniqueName; + } + + public void setUniqueName(String uniqueName) { + this.uniqueName = uniqueName; + } + @Override public String toString() { return new StringBuilder("TemplateTO[id=").append(id).append("|origUrl=").append(origUrl).append("|name").append(name).append("]").toString(); diff --git a/engine/api/src/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/com/cloud/vm/VirtualMachineManager.java index f070210b757a..a1c3809eff07 100644 --- a/engine/api/src/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/com/cloud/vm/VirtualMachineManager.java @@ -36,6 +36,7 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network; +import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.ServiceOffering; import com.cloud.storage.StoragePool; @@ -73,11 +74,12 @@ public interface Topics { * @param auxiliaryNetworks additional networks to attach the VMs to. * @param plan How to deploy the VM. * @param hyperType Hypervisor type + * @param datadiskTemplateToDiskOfferingMap data disks to be created from datadisk templates and attached to the VM * @throws InsufficientCapacityException If there are insufficient capacity to deploy this vm. */ void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, DiskOfferingInfo rootDiskOfferingInfo, List dataDiskOfferings, LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, - HypervisorType hyperType) throws InsufficientCapacityException; + HypervisorType hyperType, Map datadiskTemplateToDiskOfferingMap) throws InsufficientCapacityException; void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, LinkedHashMap> networkProfiles, DeploymentPlan plan, HypervisorType hyperType) throws InsufficientCapacityException; diff --git a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index df0b5e8e70f6..90625f7c6f5d 100644 --- a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.dc.DataCenter; @@ -45,7 +46,6 @@ import com.cloud.vm.DiskProfile; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; -import org.apache.cloudstack.framework.config.ConfigKey; /** * VolumeOrchestrationService is a PURE orchestration service on CloudStack @@ -87,7 +87,8 @@ VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, void destroyVolume(Volume volume); - DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner); + DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, + Account owner, Long deviceId); VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException; diff --git a/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java index 93f969f7a1cf..b259808513f7 100755 --- a/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java +++ b/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java @@ -36,6 +36,7 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.exception.InsufficientCapacityException; import com.cloud.hypervisor.Hypervisor; +import com.cloud.offering.DiskOffering; import com.cloud.vm.NicProfile; @Path("orchestration") @@ -56,6 +57,7 @@ public interface OrchestrationService { * @param rootDiskTags tags for the root disk * @param networks networks that this VM should join * @param rootDiskSize size the root disk in case of templates. + * @param dataDiskTemplateDiskOfferingMap disk offerings in case of data disk templates * @return VirtualMachineEntity */ @POST @@ -65,7 +67,7 @@ VirtualMachineEntity createVirtualMachine(@QueryParam("id") String id, @QueryPar @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, @QueryParam("network-nic-map") Map networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("root-disk-size") Long rootDiskSize) throws InsufficientCapacityException; + @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap) throws InsufficientCapacityException; @POST VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId, diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java index 4657316dd8a0..9353499f5a99 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java @@ -20,6 +20,8 @@ import java.util.List; +import com.cloud.hypervisor.Hypervisor.HypervisorType; + public interface EndPointSelector { EndPoint select(DataObject srcData, DataObject destData); @@ -35,5 +37,5 @@ public interface EndPointSelector { EndPoint select(Scope scope, Long storeId); - EndPoint selectHypervisorHost(Scope scope); + EndPoint selectHypervisorHostByType(Scope scope, HypervisorType htype); } diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java index 88ce932b2664..b416e9dfb99a 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java @@ -65,4 +65,6 @@ public TemplateInfo getTemplate() { void associateTemplateToZone(long templateId, Long zoneId); void associateCrosszoneTemplatesToZone(long dcId); + + AsyncCallFuture createDatadiskTemplateAsync(TemplateInfo parentTemplate, TemplateInfo dataDiskTemplate, String path, long fileSize, boolean bootable); } diff --git a/engine/api/src/org/apache/cloudstack/storage/image/datastore/ImageStoreEntity.java b/engine/api/src/org/apache/cloudstack/storage/image/datastore/ImageStoreEntity.java index 43a0f75c8d9c..223fc4d56572 100644 --- a/engine/api/src/org/apache/cloudstack/storage/image/datastore/ImageStoreEntity.java +++ b/engine/api/src/org/apache/cloudstack/storage/image/datastore/ImageStoreEntity.java @@ -18,14 +18,18 @@ */ package org.apache.cloudstack.storage.image.datastore; +import java.util.List; import java.util.Set; +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import com.cloud.agent.api.to.DatadiskTO; import com.cloud.storage.ImageStore; import com.cloud.storage.Storage.ImageFormat; @@ -43,4 +47,8 @@ public interface ImageStoreEntity extends DataStore, ImageStore { String getMountPoint(); // get the mount point on ssvm. String createEntityExtractUrl(String installPath, ImageFormat format, DataObject dataObject); // get the entity download URL + + List getDatadiskTemplates(DataObject obj); + + Void createDataDiskTemplateAsync(TemplateInfo dataDiskTemplate, String path, long fileSize, boolean bootable, AsyncCompletionCallback callback); } diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java index e15d28700ff4..0b070260ed0a 100755 --- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -29,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import java.util.UUID; @@ -40,6 +41,8 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.log4j.Logger; + import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -68,7 +71,6 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.utils.identity.ManagementServerNode; -import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; @@ -152,6 +154,7 @@ import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.rules.RulesManager; +import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; @@ -161,6 +164,7 @@ import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StoragePool; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeVO; @@ -374,7 +378,8 @@ public void registerGuru(VirtualMachine.Type type, VirtualMachineGuru guru) { @DB public void allocate(String vmInstanceName, final VirtualMachineTemplate template, ServiceOffering serviceOffering, final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, - final LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, HypervisorType hyperType) + final LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, HypervisorType hyperType, + final Map datadiskTemplateToDiskOfferingMap) throws InsufficientCapacityException { VMInstanceVO vm = _vmDao.findVMByInstanceName(vmInstanceName); @@ -412,7 +417,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Insuff if (template.getFormat() == ImageFormat.ISO) { volumeMgr.allocateRawVolume(Type.ROOT, "ROOT-" + vmFinal.getId(), rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(), - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vmFinal, template, owner); + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vmFinal, template, owner, null); } else if (template.getFormat() == ImageFormat.BAREMETAL) { // Do nothing } else { @@ -423,7 +428,19 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Insuff if (dataDiskOfferings != null) { for (DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + vmFinal.getId(), dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), - dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), vmFinal, template, owner); + dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), vmFinal, template, owner, null); + } + } + + if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { + int diskNumber = 1; + for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { + DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); + long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); + VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + vmFinal.getId() + "-" + String.valueOf(diskNumber), diskOffering, diskOfferingSize, null, null, + vmFinal, dataDiskTemplate, owner, Long.valueOf(diskNumber)); + diskNumber++; } } } @@ -437,7 +454,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) throws Insuff @Override public void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, LinkedHashMap> networks, DeploymentPlan plan, HypervisorType hyperType) throws InsufficientCapacityException { - allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(serviceOffering), new ArrayList(), networks, plan, hyperType); + allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(serviceOffering), new ArrayList(), networks, plan, hyperType, null); } private VirtualMachineGuru getVmGuru(VirtualMachine vm) { diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index 2b4995466b53..994bd92a394a 100755 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -24,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import javax.inject.Inject; @@ -45,6 +46,7 @@ import com.cloud.network.Network; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; @@ -155,7 +157,7 @@ public void destroyVolume(String volumeEntity) { @Override public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map networkNicMap, DeploymentPlan plan, - Long rootDiskSize) throws InsufficientCapacityException { + Long rootDiskSize, Map dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, // vmEntityManager); @@ -233,8 +235,19 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String dataDiskOfferings.add(dataDiskOfferingInfo); } + if (dataDiskTemplateToDiskOfferingMap != null && !dataDiskTemplateToDiskOfferingMap.isEmpty()) { + for (Entry datadiskTemplateToDiskOffering : dataDiskTemplateToDiskOfferingMap.entrySet()) { + DiskOffering diskOffering = datadiskTemplateToDiskOffering.getValue(); + if (diskOffering == null) { + throw new InvalidParameterValueException("Unable to find disk offering " + vm.getDiskOfferingId()); + } + if (diskOffering.getDiskSize() == 0) { // Custom disk offering is not supported for volumes created from datadisk templates + throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); + } + } + } _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(templateId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, - hypervisorType); + hypervisorType, dataDiskTemplateToDiskOfferingMap); return vmEntity; } @@ -299,7 +312,7 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType); + _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType, null); return vmEntity; } diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 6256e2526ef9..f453f6bd65a9 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -583,7 +583,7 @@ protected DiskProfile toDiskProfile(Volume vol, DiskOffering offering) { } @Override - public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner) { + public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner, Long deviceId) { if (size == null) { size = offering.getDiskSize(); } else { @@ -608,13 +608,17 @@ public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offeri vol.setInstanceId(vm.getId()); } - if (type.equals(Type.ROOT)) { + if (deviceId != null) { + vol.setDeviceId(deviceId); + } else if (type.equals(Type.ROOT)) { vol.setDeviceId(0l); } else { vol.setDeviceId(1l); } if (template.getFormat() == ImageFormat.ISO) { vol.setIsoId(template.getId()); + } else if(template.getTemplateType().equals(Storage.TemplateType.DATADISK)) { + vol.setTemplateId(template.getId()); } // display flag matters only for the User vms if (vm.getType() == VirtualMachine.Type.User) { diff --git a/engine/schema/src/com/cloud/storage/VMTemplateVO.java b/engine/schema/src/com/cloud/storage/VMTemplateVO.java index 9a77cbf873aa..ce8c5495fba2 100755 --- a/engine/schema/src/com/cloud/storage/VMTemplateVO.java +++ b/engine/schema/src/com/cloud/storage/VMTemplateVO.java @@ -146,6 +146,9 @@ public class VMTemplateVO implements VirtualMachineTemplate { @Column(name = "dynamically_scalable") protected boolean dynamicallyScalable; + @Column(name = "parent_template_id") + private Long parentTemplateId; + @Override public String getUniqueName() { return uniqueName; @@ -636,4 +639,13 @@ public void setUpdated(Date updated) { public Class getEntityType() { return VirtualMachineTemplate.class; } + + @Override + public Long getParentTemplateId() { + return parentTemplateId; + } + + public void setParentTemplateId(Long parentTemplateId) { + this.parentTemplateId = parentTemplateId; + } } diff --git a/engine/schema/src/com/cloud/storage/dao/VMTemplateDao.java b/engine/schema/src/com/cloud/storage/dao/VMTemplateDao.java index 2b815d8e3128..44ffcbe6dff5 100755 --- a/engine/schema/src/com/cloud/storage/dao/VMTemplateDao.java +++ b/engine/schema/src/com/cloud/storage/dao/VMTemplateDao.java @@ -75,4 +75,6 @@ public interface VMTemplateDao extends GenericDao { void loadDetails(VMTemplateVO tmpl); void saveDetails(VMTemplateVO tmpl); + + List listByParentTemplatetId(long parentTemplatetId); } diff --git a/engine/schema/src/com/cloud/storage/dao/VMTemplateDaoImpl.java b/engine/schema/src/com/cloud/storage/dao/VMTemplateDaoImpl.java index 401a4a2422e8..c96e2b106220 100755 --- a/engine/schema/src/com/cloud/storage/dao/VMTemplateDaoImpl.java +++ b/engine/schema/src/com/cloud/storage/dao/VMTemplateDaoImpl.java @@ -28,11 +28,12 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; + import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.dao.DomainDao; import com.cloud.host.Host; @@ -98,6 +99,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase implem protected SearchBuilder NameSearch; protected SearchBuilder TmpltsInZoneSearch; protected SearchBuilder ActiveTmpltSearch; + protected SearchBuilder ParentTemplateIdSearch; private SearchBuilder PublicSearch; private SearchBuilder NameAccountIdSearch; private SearchBuilder PublicIsoSearch; @@ -277,6 +279,14 @@ public List listByHypervisorType(List hyperTypes) return listBy(sc); } + @Override + public List listByParentTemplatetId(long parentTemplatetId) { + SearchCriteria sc = ParentTemplateIdSearch.create(); + sc.setParameters("parentTemplateId", parentTemplatetId); + sc.setParameters("state", VirtualMachineTemplate.State.Active); + return listBy(sc); + } + @Override public boolean configure(String name, Map params) throws ConfigurationException { boolean result = super.configure(name, params); @@ -387,6 +397,11 @@ public boolean configure(String name, Map params) throws Configu CountTemplatesByAccount.and("state", CountTemplatesByAccount.entity().getState(), SearchCriteria.Op.EQ); CountTemplatesByAccount.done(); + ParentTemplateIdSearch = createSearchBuilder(); + ParentTemplateIdSearch.and("parentTemplateId", ParentTemplateIdSearch.entity().getParentTemplateId(), SearchCriteria.Op.EQ); + ParentTemplateIdSearch.and("state", ParentTemplateIdSearch.entity().getState(), SearchCriteria.Op.EQ); + ParentTemplateIdSearch.done(); + // updateStateSearch = this.createSearchBuilder(); // updateStateSearch.and("id", updateStateSearch.entity().getId(), Op.EQ); // updateStateSearch.and("state", updateStateSearch.entity().getState(), Op.EQ); diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java index 4e6ab6ba300a..3af0904dbc64 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java @@ -42,6 +42,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.TemplateApiResult; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; @@ -67,14 +68,17 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.storage.ListTemplateAnswer; import com.cloud.agent.api.storage.ListTemplateCommand; +import com.cloud.agent.api.to.DatadiskTO; import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; +import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.ResourceAllocationException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ScopeType; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.StoragePool; @@ -134,6 +138,8 @@ public class TemplateServiceImpl implements TemplateService { ConfigurationDao _configDao; @Inject StorageCacheManager _cacheMgr; + @Inject + TemplateDataFactory imageFactory; class TemplateOpContext extends AsyncRpcContext { final TemplateObject template; @@ -578,6 +584,18 @@ protected Void createTemplateCallback(AsyncCallbackDispatcher dataDiskTemplates = new ArrayList(); + ImageStoreEntity tmpltStore = (ImageStoreEntity)parentTemplate.getDataStore(); + dataDiskTemplates = tmpltStore.getDatadiskTemplates(parentTemplate); + s_logger.error("Found " + dataDiskTemplates.size() + " Datadisk template(s) for template: " + parentTemplate.getId()); + int diskCount = 1; + VMTemplateVO template = _templateDao.findById(parentTemplate.getId()); + DataStore imageStore = parentTemplate.getDataStore(); + for (DatadiskTO dataDiskTemplate : dataDiskTemplates) { + if (dataDiskTemplate.isBootable()) + continue; + // Make an entry in vm_template table + final long templateId = _templateDao.getNextInSequence(Long.class, "id"); + VMTemplateVO templateVO = new VMTemplateVO(templateId, template.getName() + "-DataDiskTemplate-" + diskCount, template.getFormat(), false, false, false, + TemplateType.DATADISK, template.getUrl(), template.requiresHvm(), template.getBits(), template.getAccountId(), null, + template.getDisplayText() + "-DataDiskTemplate", false, 0, false, template.getHypervisorType(), null, null, false, false); + templateVO.setParentTemplateId(template.getId()); + templateVO.setSize(dataDiskTemplate.getVirtualSize()); + templateVO = _templateDao.persist(templateVO); + // Make sync call to create Datadisk templates in image store + TemplateInfo dataDiskTemplateInfo = imageFactory.getTemplate(templateVO.getId(), imageStore); + AsyncCallFuture future = createDatadiskTemplateAsync(parentTemplate, dataDiskTemplateInfo, dataDiskTemplate.getPath(), + dataDiskTemplate.getFileSize(), dataDiskTemplate.isBootable()); + try { + result = future.get(); + if (result.isSuccess()) { + // Make an entry in template_zone_ref table + if (imageStore.getScope().getScopeType() == ScopeType.REGION) { + associateTemplateToZone(templateId, null); + } else if (imageStore.getScope().getScopeType() == ScopeType.ZONE) { + Long zoneId = ((ImageStoreEntity)imageStore).getDataCenterId(); + VMTemplateZoneVO templateZone = new VMTemplateZoneVO(zoneId, templateId, new Date()); + _vmTemplateZoneDao.persist(templateZone); + } + _resourceLimitMgr.incrementResourceCount(template.getAccountId(), ResourceType.secondary_storage, templateVO.getSize()); + } else { + s_logger.error("Creation of Datadisk: " + templateVO.getId() + " failed: " + result.getResult()); + // Delete the Datadisk templates that were already created as they are now invalid + s_logger.debug("Since creation of Datadisk template: " + templateVO.getId() + " failed, delete other Datadisk templates that were created as part of parent" + + " template download"); + TemplateInfo parentTemplateInfo = imageFactory.getTemplate(templateVO.getParentTemplateId(), imageStore); + cleanupDatadiskTemplates(parentTemplateInfo); + return false; + } + } catch (Exception e) { + s_logger.error("Creation of Datadisk: " + templateVO.getId() + " failed: " + result.getResult()); + return false; + } + diskCount++; + } + // Create disk template for the bootable parent template + for (DatadiskTO dataDiskTemplate : dataDiskTemplates) { + if (dataDiskTemplate.isBootable()) { + TemplateInfo templateInfo = imageFactory.getTemplate(template.getId(), imageStore); + AsyncCallFuture templateFuture = createDatadiskTemplateAsync(parentTemplate, templateInfo, dataDiskTemplate.getPath(), + dataDiskTemplate.getFileSize(), dataDiskTemplate.isBootable()); + try { + result = templateFuture.get(); + if (!result.isSuccess()) { + s_logger.debug("Since creation of parent template: " + templateInfo.getId() + " failed, delete Datadisk templates that were created as part of parent" + + " template download"); + cleanupDatadiskTemplates(templateInfo); + } + return result.isSuccess(); + } catch (Exception e) { + s_logger.error("Creation of template: " + template.getId() + " failed: " + result.getResult()); + return false; + } + } + } + return true; + } + + private void cleanupDatadiskTemplates(TemplateInfo parentTemplateInfo) { + DataStore imageStore = parentTemplateInfo.getDataStore(); + List datadiskTemplatesToDelete = _templateDao.listByParentTemplatetId(parentTemplateInfo.getId()); + for (VMTemplateVO datadiskTemplateToDelete: datadiskTemplatesToDelete) { + s_logger.info("Delete template: " + datadiskTemplateToDelete.getId() + " from image store: " + imageStore.getName()); + AsyncCallFuture future = deleteTemplateAsync(imageFactory.getTemplate(datadiskTemplateToDelete.getId(), imageStore)); + try { + TemplateApiResult result = future.get(); + if (!result.isSuccess()) { + s_logger.warn("Failed to delete datadisk template: " + datadiskTemplateToDelete + " from image store: " + imageStore.getName() + " due to: " + result.getResult()); + break; + } + _vmTemplateZoneDao.deletePrimaryRecordsForTemplate(datadiskTemplateToDelete.getId()); + _resourceLimitMgr.decrementResourceCount(datadiskTemplateToDelete.getAccountId(), ResourceType.secondary_storage, datadiskTemplateToDelete.getSize()); + } catch (Exception e) { + s_logger.debug("Delete datadisk template failed", e); + throw new CloudRuntimeException("Delete template Failed", e); + } + } + } + @Override public AsyncCallFuture deleteTemplateAsync(TemplateInfo template) { TemplateObject to = (TemplateObject)template; @@ -887,4 +1001,71 @@ public void addSystemVMTemplatesToSecondary(DataStore store) { } } } -} + + private class CreateDataDiskTemplateContext extends AsyncRpcContext { + private final DataObject dataDiskTemplate; + private final AsyncCallFuture future; + + public CreateDataDiskTemplateContext(AsyncCompletionCallback callback, DataObject dataDiskTemplate, AsyncCallFuture future) { + super(callback); + this.dataDiskTemplate = dataDiskTemplate; + this.future = future; + } + + public AsyncCallFuture getFuture() { + return this.future; + } + } + + @Override + public AsyncCallFuture createDatadiskTemplateAsync(TemplateInfo parentTemplate, TemplateInfo dataDiskTemplate, String path, long fileSize, boolean bootable) { + AsyncCallFuture future = new AsyncCallFuture(); + // Make an entry for Datadisk template in template_store_ref table + DataStore store = parentTemplate.getDataStore(); + TemplateObject dataDiskTemplateOnStore; + if (!bootable) { + dataDiskTemplateOnStore = (TemplateObject)store.create(dataDiskTemplate); + dataDiskTemplateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.CreateOnlyRequested); + } else { + dataDiskTemplateOnStore = (TemplateObject) imageFactory.getTemplate(parentTemplate, store); + } + try { + CreateDataDiskTemplateContext context = new CreateDataDiskTemplateContext(null, dataDiskTemplateOnStore, future); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().createDatadiskTemplateCallback(null, null)).setContext(context); + ImageStoreEntity tmpltStore = (ImageStoreEntity)parentTemplate.getDataStore(); + tmpltStore.createDataDiskTemplateAsync(dataDiskTemplate, path, fileSize, bootable, caller); + } catch (CloudRuntimeException ex) { + dataDiskTemplateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); + TemplateApiResult result = new TemplateApiResult(dataDiskTemplate); + result.setResult(ex.getMessage()); + if (future != null) { + future.complete(result); + } + } + return future; + } + + protected Void createDatadiskTemplateCallback(AsyncCallbackDispatcher callback, CreateDataDiskTemplateContext context) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Performing create datadisk template cross callback after completion"); + } + DataObject dataDiskTemplate = context.dataDiskTemplate; + AsyncCallFuture future = context.getFuture(); + CreateCmdResult result = callback.getResult(); + TemplateApiResult dataDiskTemplateResult = new TemplateApiResult((TemplateObject)dataDiskTemplate); + try { + if (result.isSuccess()) { + dataDiskTemplate.processEvent(Event.OperationSuccessed, result.getAnswer()); + } else { + dataDiskTemplate.processEvent(Event.OperationFailed); + dataDiskTemplateResult.setResult(result.getResult()); + } + } catch (Exception e) { + s_logger.debug("Failed to process create datadisk template callback", e); + dataDiskTemplateResult.setResult(e.toString()); + } + future.complete(dataDiskTemplateResult); + return null; + } +} \ No newline at end of file diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java index 8da7eb7dab2f..97f09d083c40 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.storage.image.store; import java.util.Date; +import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -26,6 +27,7 @@ import org.apache.log4j.Logger; +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.ImageStoreProvider; @@ -35,6 +37,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.command.CommandResult; import org.apache.cloudstack.storage.datastore.ObjectInDataStoreManager; import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; @@ -42,6 +45,7 @@ import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; import org.apache.cloudstack.storage.to.ImageStoreTO; +import com.cloud.agent.api.to.DatadiskTO; import com.cloud.agent.api.to.DataStoreTO; import com.cloud.capacity.dao.CapacityDao; import com.cloud.storage.DataStoreRole; @@ -203,4 +207,13 @@ public String createEntityExtractUrl(String installPath, ImageFormat format, Dat return driver.createEntityExtractUrl(this, installPath, format, dataObject); } + @Override + public List getDatadiskTemplates(DataObject obj) { + return driver.getDatadiskTemplates(obj); + } + + @Override + public Void createDataDiskTemplateAsync(TemplateInfo dataDiskTemplate, String path, long fileSize, boolean bootable, AsyncCompletionCallback callback) { + return driver.createDataDiskTemplateAsync(dataDiskTemplate, path, bootable, fileSize, callback); + } } diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java index 7288d454c30f..a28498f5c7c9 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java @@ -38,6 +38,7 @@ import org.apache.cloudstack.storage.to.TemplateObjectTO; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.CreateDatadiskTemplateAnswer; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataTO; import com.cloud.exception.ConcurrentOperationException; @@ -220,6 +221,16 @@ public void processEvent(ObjectInDataStoreStateMachine.Event event, Answer answe templateVO.setSize(newTemplate.getSize()); imageDao.update(templateVO.getId(), templateVO); } + } else if (answer instanceof CreateDatadiskTemplateAnswer) { + CreateDatadiskTemplateAnswer createAnswer = (CreateDatadiskTemplateAnswer)answer; + TemplateObjectTO dataDiskTemplate = createAnswer.getDataDiskTemplate(); + TemplateDataStoreVO templateStoreRef = templateStoreDao.findByStoreTemplate(getDataStore().getId(), dataDiskTemplate.getId()); + templateStoreRef.setInstallPath(dataDiskTemplate.getPath()); + templateStoreRef.setDownloadPercent(100); + templateStoreRef.setDownloadState(Status.DOWNLOADED); + templateStoreRef.setSize(dataDiskTemplate.getSize()); + templateStoreRef.setPhysicalSize(dataDiskTemplate.getPhysicalSize()); + templateStoreDao.update(templateStoreRef.getId(), templateStoreRef); } } objectInStoreMgr.update(this, event); @@ -456,4 +467,9 @@ public boolean delete() { public Class getEntityType() { return VirtualMachineTemplate.class; } + + @Override + public Long getParentTemplateId() { + return imageVO.getParentTemplateId(); + } } diff --git a/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java b/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java index 304f9595cbf1..73c0efb323b5 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java +++ b/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java @@ -47,6 +47,7 @@ import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage.TemplateType; @@ -66,6 +67,7 @@ public class DefaultEndPointSelector implements EndPointSelector { "select h.id from host h, storage_pool_host_ref s where h.status = 'Up' and h.type = 'Routing' and h.resource_state = 'Enabled' and" + " h.id = s.host_id and s.pool_id = ? "; private String findOneHypervisorHostInScope = "select h.id from host h where h.status = 'Up' and h.hypervisor_type is not null "; + private String findOneHypervisorHostInScopeByType = "select h.id from host h where h.status = 'Up' and h.hypervisor_type = ? "; protected boolean moveBetweenPrimaryImage(DataStore srcStore, DataStore destStore) { DataStoreRole srcRole = srcStore.getRole(); @@ -346,9 +348,13 @@ public List selectAll(DataStore store) { } @Override - public EndPoint selectHypervisorHost(Scope scope) { + public EndPoint selectHypervisorHostByType(Scope scope, HypervisorType htype) { StringBuilder sbuilder = new StringBuilder(); - sbuilder.append(findOneHypervisorHostInScope); + if (htype != null) { + sbuilder.append(findOneHypervisorHostInScopeByType); + } else { + sbuilder.append(findOneHypervisorHostInScope); + } if (scope.getScopeType() == ScopeType.ZONE) { sbuilder.append(" and h.data_center_id = "); sbuilder.append(scope.getScopeId()); @@ -366,6 +372,9 @@ public EndPoint selectHypervisorHost(Scope scope) { try { pstmt = txn.prepareStatement(sql); + if (htype != null) { + pstmt.setString(1, htype.toString()); + } rs = pstmt.executeQuery(); while (rs.next()) { long id = rs.getLong(1); diff --git a/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java index 7ed11ecd0e1b..b170c757984f 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java @@ -20,7 +20,9 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Map; import javax.inject.Inject; @@ -33,6 +35,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.framework.async.AsyncRpcContext; @@ -43,18 +46,27 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.apache.cloudstack.storage.endpoint.DefaultEndPointSelector; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand; import com.cloud.agent.api.storage.DownloadAnswer; +import com.cloud.agent.api.storage.GetDatadisksAnswer; +import com.cloud.agent.api.storage.GetDatadisksCommand; import com.cloud.agent.api.storage.Proxy; +import com.cloud.agent.api.to.DatadiskTO; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.VMTemplateStorageResourceAssoc; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.download.DownloadMonitor; +import com.cloud.user.ResourceLimitService; +import com.cloud.user.dao.AccountDao; public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { private static final Logger s_logger = Logger.getLogger(BaseImageStoreDriverImpl.class); @@ -69,9 +81,17 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { @Inject TemplateDataStoreDao _templateStoreDao; @Inject + VMTemplateDetailsDao _templateDetailsDao; + @Inject EndPointSelector _epSelector; @Inject - ConfigurationDao configDao; + ConfigurationDao configDao;; + @Inject + DefaultEndPointSelector _defaultEpSelector; + @Inject + AccountDao _accountDao; + @Inject + ResourceLimitService _resourceLimitMgr; protected String _proxy = null; protected Proxy getHttpProxy() { @@ -143,6 +163,7 @@ protected Void createTemplateAsyncCallback(AsyncCallbackDispatcher callback) { } + + @Override + public List getDatadiskTemplates(DataObject obj) { + List dataDiskDetails = new ArrayList(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Get the data disks present in the OVA template"); + } + DataStore store = obj.getDataStore(); + GetDatadisksCommand cmd = new GetDatadisksCommand(obj.getTO()); + EndPoint ep = _defaultEpSelector.selectHypervisorHostByType(store.getScope(), HypervisorType.VMware); + Answer answer = null; + if (ep == null) { + String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + s_logger.error(errMsg); + answer = new Answer(cmd, false, errMsg); + } else { + answer = ep.sendMessage(cmd); + } + if (answer != null && answer.getResult()) { + GetDatadisksAnswer getDatadisksAnswer = (GetDatadisksAnswer)answer; + dataDiskDetails = getDatadisksAnswer.getDataDiskDetails(); // Details - Disk path, virtual size, bootable + } + return dataDiskDetails; + } + + @Override + public Void createDataDiskTemplateAsync(TemplateInfo dataDiskTemplate, String path, boolean bootable, long fileSize, + AsyncCompletionCallback callback) { + Answer answer = null; + String errMsg = null; + if (s_logger.isDebugEnabled()) { + s_logger.debug("Create Datadisk template: " + dataDiskTemplate.getId()); + } + CreateDatadiskTemplateCommand cmd = new CreateDatadiskTemplateCommand(dataDiskTemplate.getTO(), path, fileSize, bootable); + EndPoint ep = _defaultEpSelector.selectHypervisorHostByType(dataDiskTemplate.getDataStore().getScope(), HypervisorType.VMware); + if (ep == null) { + errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + s_logger.error(errMsg); + answer = new Answer(cmd, false, errMsg); + } else { + answer = ep.sendMessage(cmd); + } + if (answer != null && !answer.getResult()) { + errMsg = answer.getDetails(); + } + CreateCmdResult result = new CreateCmdResult(null, answer); + result.setResult(errMsg); + callback.complete(result); + return null; + } } diff --git a/engine/storage/src/org/apache/cloudstack/storage/image/ImageStoreDriver.java b/engine/storage/src/org/apache/cloudstack/storage/image/ImageStoreDriver.java index fa7ea372f77c..a262c9b71fc0 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/image/ImageStoreDriver.java +++ b/engine/storage/src/org/apache/cloudstack/storage/image/ImageStoreDriver.java @@ -18,12 +18,22 @@ */ package org.apache.cloudstack.storage.image; +import java.util.List; + +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import com.cloud.agent.api.to.DatadiskTO; import com.cloud.storage.Storage.ImageFormat; public interface ImageStoreDriver extends DataStoreDriver { String createEntityExtractUrl(DataStore store, String installPath, ImageFormat format, DataObject dataObject); + + List getDatadiskTemplates(DataObject obj); + + Void createDataDiskTemplateAsync(TemplateInfo dataDiskTemplate, String path, boolean bootable, long fileSize, AsyncCompletionCallback callback); } diff --git a/engine/storage/src/org/apache/cloudstack/storage/image/TemplateEntityImpl.java b/engine/storage/src/org/apache/cloudstack/storage/image/TemplateEntityImpl.java index c1aa8c2f0d49..5e038b8914c9 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/image/TemplateEntityImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/image/TemplateEntityImpl.java @@ -290,4 +290,10 @@ public long getVirtualSize() { public Class getEntityType() { return VirtualMachineTemplate.class; } + + @Override + public Long getParentTemplateId() { + // TODO Auto-generated method stub + return null; + } } diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java index f78f370da318..9e16e35b8257 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java @@ -27,7 +27,9 @@ import com.cloud.agent.api.DeleteVMSnapshotCommand; import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand; import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; +import com.cloud.agent.api.storage.GetDatadisksCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; public interface VmwareStorageManager { @@ -49,6 +51,10 @@ public interface VmwareStorageManager { Answer execute(VmwareHostService hostService, RevertToVMSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, CreateDatadiskTemplateCommand cmd); + + Answer execute(VmwareHostService hostService, GetDatadisksCommand cmd); + boolean execute(VmwareHostService hostService, CreateEntityDownloadURLCommand cmd); public void createOva(String path, String name); diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java index c1b907e120eb..b1001f8f4ed6 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java @@ -57,13 +57,18 @@ import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateDatadiskTemplateAnswer; +import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand; import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; +import com.cloud.agent.api.storage.GetDatadisksAnswer; +import com.cloud.agent.api.storage.GetDatadisksCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataStoreTO; import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.DatadiskTO; import com.cloud.agent.api.to.NfsTO; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.hypervisor.vmware.mo.CustomFieldConstants; @@ -80,6 +85,7 @@ import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageLayer; import com.cloud.storage.Volume; +import com.cloud.storage.resource.VmwareStorageLayoutHelper; import com.cloud.storage.template.OVAProcessor; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; @@ -543,6 +549,171 @@ public Answer execute(VmwareHostService hostService, CreateVolumeFromSnapshotCom return new CreateVolumeFromSnapshotAnswer(cmd, success, details, newVolumeName); } + @Override + public Answer execute(VmwareHostService hostService, GetDatadisksCommand cmd) { + List disks = new ArrayList(); + DataTO srcData = cmd.getData(); + TemplateObjectTO template = (TemplateObjectTO)srcData; + DataStoreTO srcStore = srcData.getDataStore(); + if (!(srcStore instanceof NfsTO)) { + return new CreateDatadiskTemplateAnswer("Unsupported protocol"); + } + NfsTO nfsImageStore = (NfsTO)srcStore; + String secondaryStorageUrl = nfsImageStore.getUrl(); + assert (secondaryStorageUrl != null); + String secondaryStorageUuid = HypervisorHostHelper.getSecondaryDatastoreUUID(secondaryStorageUrl).replace("-", ""); + String templateUrl = secondaryStorageUrl + "/" + srcData.getPath(); + Pair templateInfo = VmwareStorageLayoutHelper.decodeTemplateRelativePathAndNameFromUrl(secondaryStorageUrl, templateUrl, template.getName()); + String templateRelativeFolderPath = templateInfo.first(); + + VmwareContext context = hostService.getServiceContext(cmd); + try { + VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); + + ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, secondaryStorageUuid); + DatastoreMO datastoreMo = new DatastoreMO(context, morDs); + + String secondaryMountPoint = _mountService.getMountPoint(secondaryStorageUrl); + s_logger.info("Secondary storage mount point: " + secondaryMountPoint); + + String srcOVAFileName = VmwareStorageLayoutHelper.getTemplateOnSecStorageFilePath(secondaryMountPoint, templateRelativeFolderPath, templateInfo.second(), + ImageFormat.OVA.getFileExtension()); + + String ovfFilePath = getOVFFilePath(srcOVAFileName); + if (ovfFilePath == null) { + Script command = new Script("tar", 0, s_logger); + command.add("--no-same-owner"); + command.add("-xf", srcOVAFileName); + command.setWorkDir(secondaryMountPoint + "/" + templateRelativeFolderPath); + s_logger.info("Executing command: " + command.toString()); + String result = command.execute(); + if (result != null) { + String msg = "Unable to unpack snapshot OVA file at: " + srcOVAFileName; + s_logger.error(msg); + throw new Exception(msg); + } + } + + ovfFilePath = getOVFFilePath(srcOVAFileName); + if (ovfFilePath == null) { + String msg = "Unable to locate OVF file in template package directory: " + srcOVAFileName; + s_logger.error(msg); + throw new Exception(msg); + } + + s_logger.debug("Reading OVF " + ovfFilePath + " to retrive the number of disks present in OVA"); + List> ovfVolumeDetails = HypervisorHostHelper.readOVF(hyperHost, ovfFilePath, datastoreMo); + + // Get OVA disk details + for (Pair ovfVolumeDetail : ovfVolumeDetails) { + String dataDiskPath = ovfVolumeDetail.first(); + String diskName = dataDiskPath.substring((dataDiskPath.lastIndexOf(File.separator)) + 1); + Pair diskDetails = new OVAProcessor().getDiskDetails(ovfFilePath, diskName); + disks.add(new DatadiskTO(dataDiskPath, diskDetails.first(), diskDetails.second(), ovfVolumeDetail.second())); + } + } catch (Exception e) { + String msg = "Get Datadisk Template Count failed due to " + e.getMessage(); + s_logger.error(msg); + return new GetDatadisksAnswer(msg); + } + return new GetDatadisksAnswer(disks); + } + + @Override + public Answer execute(VmwareHostService hostService, CreateDatadiskTemplateCommand cmd) { + TemplateObjectTO diskTemplate = new TemplateObjectTO(); + TemplateObjectTO dataDiskTemplate = (TemplateObjectTO)cmd.getDataDiskTemplate(); + DataStoreTO dataStore = dataDiskTemplate.getDataStore(); + if (!(dataStore instanceof NfsTO)) { + return new CreateDatadiskTemplateAnswer("Unsupported protocol"); + } + NfsTO nfsImageStore = (NfsTO)dataStore; + String secondaryStorageUrl = nfsImageStore.getUrl(); + assert (secondaryStorageUrl != null); + String secondaryStorageUuid = HypervisorHostHelper.getSecondaryDatastoreUUID(secondaryStorageUrl).replace("-", ""); + + VmwareContext context = hostService.getServiceContext(cmd); + try { + VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); + + ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, secondaryStorageUuid); + DatastoreMO datastoreMo = new DatastoreMO(context, morDs); + + String secondaryMountPoint = _mountService.getMountPoint(secondaryStorageUrl); + s_logger.info("Secondary storage mount point: " + secondaryMountPoint); + + long templateId = dataDiskTemplate.getId(); + String templateUniqueName = dataDiskTemplate.getUniqueName(); + String dataDiskPath = cmd.getPath(); + long virtualSize = dataDiskTemplate.getSize(); + long fileSize = cmd.getFileSize(); + String diskName = dataDiskPath.substring((dataDiskPath.lastIndexOf(File.separator)) + 1); + long physicalSize = new File(dataDiskPath).length(); + String dataDiskTemplateFolderPath = getTemplateRelativeDirInSecStorage(dataDiskTemplate.getAccountId(), dataDiskTemplate.getId()); + String dataDiskTemplateFolderFullPath = secondaryMountPoint + "/" + dataDiskTemplateFolderPath; + String ovfName = diskName.substring(0, diskName.lastIndexOf("-")); + String datastorePath = String.format("[%s] %s", datastoreMo.getName(), dataDiskTemplateFolderPath); + + if (!cmd.getBootable()) { + // Create folder to hold datadisk template + synchronized (dataDiskTemplateFolderPath.intern()) { + Script command = new Script(false, "mkdir", _timeout, s_logger); + command.add("-p"); + command.add(dataDiskTemplateFolderFullPath); + String result = command.execute(); + if (result != null) { + String msg = "Unable to prepare template directory: " + dataDiskTemplateFolderPath + ", storage: " + secondaryStorageUrl + ", error msg: " + result; + s_logger.error(msg); + throw new Exception(msg); + } + } + // Move Datadisk VMDK from parent template folder to Datadisk template folder + synchronized (dataDiskPath.intern()) { + Script command = new Script(false, "mv", _timeout, s_logger); + command.add(dataDiskPath); + command.add(dataDiskTemplateFolderFullPath); + String result = command.execute(); + if (result != null) { + String msg = "Unable to copy VMDK from parent template folder to datadisk template folder" + ", error msg: " + result; + s_logger.error(msg); + throw new Exception(msg); + } + } + } else { + // Delete original OVA as a new OVA will be created for the root disk template + String rootDiskTemplatePath = dataDiskTemplate.getPath(); + String rootDiskTemplateFullPath = secondaryMountPoint + "/" + rootDiskTemplatePath; + synchronized (rootDiskTemplateFullPath.intern()) { + Script command = new Script(false, "rm", _timeout, s_logger); + command.add(rootDiskTemplateFullPath); + String result = command.execute(); + if (result != null) { + String msg = "Unable to delete original OVA" + ", error msg: " + result; + s_logger.error(msg); + throw new Exception(msg); + } + } + } + + // Create OVF for Datadisk + s_logger.debug("Creating OVF file for datadisk " + diskName + " in " + dataDiskTemplateFolderFullPath); + HypervisorHostHelper.createOvfFile(hyperHost, diskName, ovfName, datastorePath, dataDiskTemplateFolderFullPath, virtualSize, fileSize, morDs); + + postCreatePrivateTemplate(dataDiskTemplateFolderFullPath, templateId, templateUniqueName, physicalSize, virtualSize); + writeMetaOvaForTemplate(dataDiskTemplateFolderFullPath, ovfName + ".ovf", diskName, templateUniqueName, physicalSize); + + diskTemplate.setId(templateId); + diskTemplate.setPath(dataDiskTemplateFolderPath + "/" + templateUniqueName + ".ova"); + diskTemplate.setSize(virtualSize); + diskTemplate.setPhysicalSize(physicalSize); + } catch (Exception e) { + String msg = "Create Datadisk template failed due to " + e.getMessage(); + s_logger.error(msg); + return new CreateDatadiskTemplateAnswer(msg); + } + return new CreateDatadiskTemplateAnswer(diskTemplate); + } + // templateName: name in secondary storage // templateUuid: will be used at hypervisor layer private void copyTemplateFromSecondaryToPrimary(VmwareHypervisorHost hyperHost, DatastoreMO datastoreMo, String secondaryStorageUrl, diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 0024b441d1dd..2991e85b7a3b 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -188,8 +188,12 @@ import com.cloud.agent.api.routing.SetSourceNatCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateDatadiskTemplateAnswer; +import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; import com.cloud.agent.api.storage.DestroyCommand; +import com.cloud.agent.api.storage.GetDatadisksAnswer; +import com.cloud.agent.api.storage.GetDatadisksCommand; import com.cloud.agent.api.storage.MigrateVolumeAnswer; import com.cloud.agent.api.storage.MigrateVolumeCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; @@ -430,6 +434,10 @@ public Answer executeRequest(Command cmd) { answer = execute((GetStorageStatsCommand)cmd); } else if (clz == PrimaryStorageDownloadCommand.class) { answer = execute((PrimaryStorageDownloadCommand)cmd); + } else if (clz == CreateDatadiskTemplateCommand.class) { + answer = execute((CreateDatadiskTemplateCommand)cmd); + } else if (clz == GetDatadisksCommand.class) { + answer = execute((GetDatadisksCommand)cmd); } else if (clz == GetVncPortCommand.class) { answer = execute((GetVncPortCommand)cmd); } else if (clz == SetupCommand.class) { @@ -3724,6 +3732,44 @@ public PrimaryStorageDownloadAnswer execute(PrimaryStorageDownloadCommand cmd) { } } + protected GetDatadisksAnswer execute(GetDatadisksCommand cmd) { + if (s_logger.isInfoEnabled()) { + s_logger.info("Executing resource GetDatadisksCommand: " + _gson.toJson(cmd)); + } + try { + VmwareContext context = getServiceContext(); + VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + return (GetDatadisksAnswer)mgr.getStorageManager().execute(this, cmd); + } catch (Throwable e) { + if (e instanceof RemoteException) { + s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context"); + invalidateServiceContext(); + } + String msg = "GetDatadisksCommand failed due to " + VmwareHelper.getExceptionMessage(e); + s_logger.error(msg, e); + return new GetDatadisksAnswer(msg); + } + } + + protected CreateDatadiskTemplateAnswer execute(CreateDatadiskTemplateCommand cmd) { + if (s_logger.isInfoEnabled()) { + s_logger.info("Executing resource CreateDatadiskTemplatesCommand: " + _gson.toJson(cmd)); + } + try { + VmwareContext context = getServiceContext(); + VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + return (CreateDatadiskTemplateAnswer)mgr.getStorageManager().execute(this, cmd); + } catch (Throwable e) { + if (e instanceof RemoteException) { + s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context"); + invalidateServiceContext(); + } + String msg = "CreateDatadiskTemplatesCommand failed due to " + VmwareHelper.getExceptionMessage(e); + s_logger.error(msg, e); + return new CreateDatadiskTemplateAnswer(msg); + } + } + protected Answer execute(PvlanSetupCommand cmd) { // Pvlan related operations are performed in the start/stop command paths // for vmware. This function is implemented to support mgmt layer code diff --git a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java index f63377499009..a7f647b5314f 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java +++ b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java @@ -33,7 +33,9 @@ import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand; import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; +import com.cloud.agent.api.storage.GetDatadisksCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; import com.cloud.hypervisor.vmware.manager.VmwareHostService; import com.cloud.hypervisor.vmware.manager.VmwareStorageManager; @@ -98,6 +100,10 @@ public Answer executeRequest(Command cmd) { answer = storageSubsystemHandler.handleStorageCommands((StorageSubSystemCommand)cmd); } else if (cmd instanceof CreateEntityDownloadURLCommand) { answer = execute((CreateEntityDownloadURLCommand)cmd); + } else if (cmd instanceof CreateDatadiskTemplateCommand) { + answer = execute((CreateDatadiskTemplateCommand)cmd); + } else if (cmd instanceof GetDatadisksCommand) { + answer = execute((GetDatadisksCommand)cmd); } else { answer = _resource.defaultAction(cmd); } @@ -174,6 +180,14 @@ private Answer execute(CreateVolumeFromSnapshotCommand cmd) { return _storageMgr.execute(this, cmd); } + private Answer execute(CreateDatadiskTemplateCommand cmd) { + return _storageMgr.execute(this, cmd); + } + + private Answer execute(GetDatadisksCommand cmd) { + return _storageMgr.execute(this, cmd); + } + @Override public VmwareContext getServiceContext(Command cmd) { String guid = cmd.getContextParam("guid"); diff --git a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java index ff893b2cc46e..131fe0a92f3e 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java +++ b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java @@ -456,7 +456,7 @@ public Answer cloneVolumeFromBaseTemplate(CopyCommand cmd) { vmMo = new ClusterMO(context, morCluster).findVmOnHyperHost(vmdkName); assert (vmMo != null); - vmdkFileBaseName = vmMo.getVmdkFileBaseNames().get(0); // TO-DO: Support for base template containing multiple disks + vmdkFileBaseName = vmMo.getVmdkFileBaseNames().get(0); s_logger.info("Move volume out of volume-wrapper VM "); String[] vmwareLayoutFilePair = VmwareStorageLayoutHelper.getVmdkFilePairDatastorePath(dsMo, vmdkName, vmdkFileBaseName, VmwareStorageLayoutType.VMWARE, !_fullCloneFlag); String[] legacyCloudStackLayoutFilePair = VmwareStorageLayoutHelper.getVmdkFilePairDatastorePath(dsMo, vmdkName, vmdkFileBaseName, VmwareStorageLayoutType.CLOUDSTACK_LEGACY, !_fullCloneFlag); @@ -471,7 +471,9 @@ public Answer cloneVolumeFromBaseTemplate(CopyCommand cmd) { vmMo.destroy(); String srcFile = dsMo.getDatastorePath(vmdkName, true); - dsMo.deleteFile(srcFile, dcMo.getMor(), true); + if (dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmdkName)) { + dsMo.deleteFolder(srcFile, dcMo.getMor()); + } } // restoreVM - move the new ROOT disk into corresponding VM folder String vmInternalCSName = volume.getVmName(); diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/XenServerGuru.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/XenServerGuru.java index 89e4ab5d94b6..40fc1d8f2d90 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/XenServerGuru.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/XenServerGuru.java @@ -145,7 +145,7 @@ public Pair getCommandHostDelegation(long hostId, Command cmd) { DataStoreTO destStore = destData.getDataStore(); if (srcStore instanceof NfsTO && destStore instanceof NfsTO) { HostVO host = hostDao.findById(hostId); - EndPoint ep = endPointSelector.selectHypervisorHost(new ZoneScope(host.getDataCenterId())); + EndPoint ep = endPointSelector.selectHypervisorHostByType(new ZoneScope(host.getDataCenterId()), null); host = hostDao.findById(ep.getId()); hostDao.loadDetails(host); String snapshotHotFixVersion = host.getDetail(XenserverConfigs.XS620HotFix); diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 250f5a9085bd..6d78d62eac90 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -1258,6 +1258,11 @@ public VirtualMachineTemplate findTemplateById(Long templateId) { return ApiDBUtils.findTemplateById(templateId); } + @Override + public DiskOfferingVO findDiskOfferingById(Long diskOfferingId) { + return ApiDBUtils.findDiskOfferingById(diskOfferingId); + } + @Override public VpnUsersResponse createVpnUserResponse(VpnUser vpnUser) { VpnUsersResponse vpnResponse = new VpnUsersResponse(); diff --git a/server/src/com/cloud/api/query/QueryManagerImpl.java b/server/src/com/cloud/api/query/QueryManagerImpl.java index 8e020fc56a04..96f759b0f878 100644 --- a/server/src/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/com/cloud/api/query/QueryManagerImpl.java @@ -2812,6 +2812,7 @@ private Pair, Integer> searchForTemplatesInternalIAM(ListTe Long id = cmd.getId(); Map tags = cmd.getTags(); boolean showRemovedTmpl = cmd.getShowRemoved(); + Long parentTemplateId = cmd.getParentTemplateId(); Account caller = CallContext.current().getCallingAccount(); // TODO: listAll flag has some conflicts with TemplateFilter parameter @@ -2841,7 +2842,7 @@ private Pair, Integer> searchForTemplatesInternalIAM(ListTe return searchForTemplatesInternalIAM(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false, null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType, showDomr, - cmd.listInReadyState(), permittedDomains, permittedAccounts, permittedResources, isRecursive, caller, listProjectResourcesCriteria, tags, showRemovedTmpl); + cmd.listInReadyState(), permittedDomains, permittedAccounts, permittedResources, isRecursive, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, parentTemplateId); } // Temporarily disable this method which used IAM model to do template list @@ -2850,7 +2851,7 @@ private Pair, Integer> searchForTemplatesInternalIAM(Long t Long startIndex, Long zoneId, HypervisorType hyperType, boolean showDomr, boolean onlyReady, List permittedDomains, List permittedAccounts, List permittedResources, boolean isRecursive, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria, - Map tags, boolean showRemovedTmpl) { + Map tags, boolean showRemovedTmpl, Long parentTemplateId) { // check if zone is configured, if not, just return empty list List hypers = null; @@ -3047,6 +3048,10 @@ private Pair, Integer> searchForTemplatesInternalIAM(Long t sc.addAnd("dataCenterId", SearchCriteria.Op.SC, zoneSc); } + if (parentTemplateId != null) { + sc.addAnd("parentTemplateId", SearchCriteria.Op.EQ, parentTemplateId); + } + // don't return removed template, this should not be needed since we // changed annotation for removed field in TemplateJoinVO. // sc.addAnd("removed", SearchCriteria.Op.NULL); @@ -3546,7 +3551,7 @@ private Pair, Integer> searchForIsosInternalIAM(ListIsosCmd return searchForTemplatesInternalIAM(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType, true, - cmd.listInReadyState(), permittedDomains, permittedAccounts, permittedResources, isRecursive, caller, listProjectResourcesCriteria, tags, showRemovedISO); + cmd.listInReadyState(), permittedDomains, permittedAccounts, permittedResources, isRecursive, caller, listProjectResourcesCriteria, tags, showRemovedISO, null); } @Override diff --git a/server/src/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/TemplateJoinDaoImpl.java index 80ef0f6ed7d9..d245d17a793e 100644 --- a/server/src/com/cloud/api/query/dao/TemplateJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/TemplateJoinDaoImpl.java @@ -180,6 +180,10 @@ public TemplateResponse newTemplateResponse(ResponseView view, TemplateJoinVO te } templateResponse.setTemplateTag(template.getTemplateTag()); + if (template.getParentTemplateId() != null) { + templateResponse.setParentTemplateId(template.getParentTemplateUuid()); + } + // set details map if (template.getDetailName() != null) { Map details = new HashMap(); diff --git a/server/src/com/cloud/api/query/vo/TemplateJoinVO.java b/server/src/com/cloud/api/query/vo/TemplateJoinVO.java index 834a9cedd071..62a103f61227 100644 --- a/server/src/com/cloud/api/query/vo/TemplateJoinVO.java +++ b/server/src/com/cloud/api/query/vo/TemplateJoinVO.java @@ -209,6 +209,12 @@ public class TemplateJoinVO extends BaseViewVO implements ControlledViewEntity { @Column(name = "destroyed") boolean destroyed = false; + @Column(name = "parent_template_id") + private Long parentTemplateId; + + @Column(name = "parent_template_uuid") + private String parentTemplateUuid; + @Column(name = "lp_account_id") private Long sharedAccountId; @@ -543,6 +549,14 @@ public State getTemplateState() { return templateState; } + public Long getParentTemplateId() { + return parentTemplateId; + } + + public String getParentTemplateUuid() { + return parentTemplateUuid; + } + @Override public Class getEntityType() { return VirtualMachineTemplate.class; diff --git a/server/src/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/com/cloud/network/as/AutoScaleManagerImpl.java index 09c669470165..044eee5dd80e 100644 --- a/server/src/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/com/cloud/network/as/AutoScaleManagerImpl.java @@ -1331,18 +1331,18 @@ private long createNewVM(AutoScaleVmGroupVO asGroup) { vm = _userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, - null, true, null, null, null, null); + null, true, null, null, null, null, null); } else { if (zone.isSecurityGroupEnabled()) { vm = _userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, null, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, - null, null, true, null, null, null, null); + null, null, true, null, null, null, null, null); } else { vm = _userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), - null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null); + null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null, null); } } diff --git a/server/src/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/com/cloud/template/HypervisorTemplateAdapter.java index 51dedf76973d..8bef4a54ba2e 100755 --- a/server/src/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/com/cloud/template/HypervisorTemplateAdapter.java @@ -48,6 +48,7 @@ import org.apache.cloudstack.framework.async.AsyncRpcContext; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; @@ -68,6 +69,7 @@ import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateZoneVO; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.download.DownloadMonitor; import com.cloud.user.Account; @@ -96,6 +98,8 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { @Inject AlertManager alertMgr; @Inject + TemplateDataStoreDao _tmplStoreDao; + @Inject VMTemplateZoneDao templateZoneDao; @Inject EndPointSelector _epSelector; @@ -103,6 +107,8 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { DataCenterDao _dcDao; @Inject MessageBus _messageBus; + @Inject + VMTemplateDao _templateDao; @Override public String getName() { @@ -334,9 +340,10 @@ protected Void createTemplateAsyncCallBack(AsyncCallbackDispatcher imageStores = templateMgr.getImageStoreByTemplate(template.getId(), profile.getZoneId()); @@ -372,29 +379,71 @@ public boolean delete(TemplateProfile profile) { UsageEventUtils.publishUsageEvent(eventType, template.getAccountId(), sZoneId, template.getId(), null, null, null); } - s_logger.info("Delete template from image store: " + imageStore.getName()); - AsyncCallFuture future = imageService.deleteTemplateAsync(imageFactory.getTemplate(template.getId(), imageStore)); - try { - TemplateApiResult result = future.get(); - success = result.isSuccess(); - if (!success) { - s_logger.warn("Failed to delete the template " + template + " from the image store: " + imageStore.getName() + " due to: " + result.getResult()); - break; + boolean dataDiskDeletetionResult = true; + List dataDiskTemplates = _templateDao.listByParentTemplatetId(template.getId()); + if (dataDiskTemplates != null && dataDiskTemplates.size() > 0) { + s_logger.info("Template: " + template.getId() + " has Datadisk template(s) associated with it. Delete Datadisk templates before deleting the template"); + for (VMTemplateVO dataDiskTemplate : dataDiskTemplates) { + s_logger.info("Delete Datadisk template: " + dataDiskTemplate.getId() + " from image store: " + imageStore.getName()); + AsyncCallFuture future = imageService.deleteTemplateAsync(imageFactory.getTemplate(dataDiskTemplate.getId(), imageStore)); + try { + TemplateApiResult result = future.get(); + dataDiskDeletetionResult = result.isSuccess(); + if (!dataDiskDeletetionResult) { + s_logger.warn("Failed to delete datadisk template: " + dataDiskTemplate + " from image store: " + imageStore.getName() + " due to: " + + result.getResult()); + break; + } + // Remove from template_zone_ref + List templateZones = templateZoneDao.listByZoneTemplate(sZoneId, dataDiskTemplate.getId()); + if (templateZones != null) { + for (VMTemplateZoneVO templateZone : templateZones) { + templateZoneDao.remove(templateZone.getId()); + } + } + // Mark datadisk template as Inactive + List iStores = templateMgr.getImageStoreByTemplate(dataDiskTemplate.getId(), null); + if (iStores == null || iStores.size() == 0) { + dataDiskTemplate.setState(VirtualMachineTemplate.State.Inactive); + _tmpltDao.update(dataDiskTemplate.getId(), dataDiskTemplate); + } + // Decrement total secondary storage space used by the account + _resourceLimitMgr.recalculateResourceCount(dataDiskTemplate.getAccountId(), account.getDomainId(), ResourceType.secondary_storage.getOrdinal()); + } catch (Exception e) { + s_logger.debug("Delete datadisk template failed", e); + throw new CloudRuntimeException("Delete datadisk template failed", e); + } } + } - // remove from template_zone_ref - List templateZones = templateZoneDao.listByZoneTemplate(sZoneId, template.getId()); - if (templateZones != null) { - for (VMTemplateZoneVO templateZone : templateZones) { - templateZoneDao.remove(templateZone.getId()); + if (dataDiskDeletetionResult) { + s_logger.info("Delete template: " + template.getId() + " from image store: " + imageStore.getName()); + AsyncCallFuture future = imageService.deleteTemplateAsync(imageFactory.getTemplate(template.getId(), imageStore)); + try { + TemplateApiResult result = future.get(); + success = result.isSuccess(); + if (!success) { + s_logger.warn("Failed to delete the template: " + template + " from the image store: " + imageStore.getName() + " due to: " + result.getResult()); + break; } + + // remove from template_zone_ref + List templateZones = templateZoneDao.listByZoneTemplate(sZoneId, template.getId()); + if (templateZones != null) { + for (VMTemplateZoneVO templateZone : templateZones) { + templateZoneDao.remove(templateZone.getId()); + } + } + } catch (InterruptedException e) { + s_logger.debug("Delete template Failed", e); + throw new CloudRuntimeException("Delete template Failed", e); + } catch (ExecutionException e) { + s_logger.debug("Delete template Failed", e); + throw new CloudRuntimeException("Delete template Failed", e); } - } catch (InterruptedException e) { - s_logger.debug("delete template Failed", e); - throw new CloudRuntimeException("delete template Failed", e); - } catch (ExecutionException e) { - s_logger.debug("delete template Failed", e); - throw new CloudRuntimeException("delete template Failed", e); + } else { + s_logger.warn("Template: " + template.getId() + " won't be deleted from image store: " + imageStore.getName() + " because deletion of one of the Datadisk" + + " templates that belonged to the template failed"); } } } @@ -407,7 +456,7 @@ public boolean delete(TemplateProfile profile) { // delete all cache entries for this template List cacheTmpls = imageFactory.listTemplateOnCache(template.getId()); for (TemplateInfo tmplOnCache : cacheTmpls) { - s_logger.info("Delete template from image cache store: " + tmplOnCache.getDataStore().getName()); + s_logger.info("Delete template: " + tmplOnCache.getId() + " from image cache store: " + tmplOnCache.getDataStore().getName()); tmplOnCache.delete(); } @@ -420,7 +469,6 @@ public boolean delete(TemplateProfile profile) { // Decrement the number of templates and total secondary storage // space used by the account - Account account = _accountDao.findByIdIncludingRemoved(template.getAccountId()); _resourceLimitMgr.decrementResourceCount(template.getAccountId(), ResourceType.template); _resourceLimitMgr.recalculateResourceCount(template.getAccountId(), account.getDomainId(), ResourceType.secondary_storage.getOrdinal()); diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index ead841fb1c69..8083be75a286 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -656,12 +656,33 @@ public boolean copy(long userId, VMTemplateVO template, DataStore srcSecStore, D UsageEventUtils.publishUsageEvent(copyEventType, account.getId(), dstZoneId, tmpltId, null, null, null, srcTmpltStore.getPhysicalSize(), srcTmpltStore.getSize(), template.getClass().getName(), template.getUuid()); } - return true; + // Copy every Datadisk template that belongs to the template to Destination zone + List dataDiskTemplates = _tmpltDao.listByParentTemplatetId(template.getId()); + if (dataDiskTemplates != null && !dataDiskTemplates.isEmpty()) { + for (VMTemplateVO dataDiskTemplate : dataDiskTemplates) { + s_logger.debug("Copying " + dataDiskTemplates.size() + " for source template " + template.getId() + ". Copy all Datadisk templates to destination datastore " + dstSecStore.getName()); + TemplateInfo srcDataDiskTemplate = _tmplFactory.getTemplate(dataDiskTemplate.getId(), srcSecStore); + AsyncCallFuture dataDiskCopyFuture = _tmpltSvr.copyTemplate(srcDataDiskTemplate, dstSecStore); + try { + TemplateApiResult dataDiskCopyResult = dataDiskCopyFuture.get(); + if (dataDiskCopyResult.isFailed()) { + s_logger.error("Copy of datadisk template: " + srcDataDiskTemplate.getId() + " to image store: " + dstSecStore.getName() + + " failed with error: " + dataDiskCopyResult.getResult() + " , will try copying the next one"); + continue; // Continue to copy next Datadisk template + } + _tmpltDao.addTemplateToZone(dataDiskTemplate, dstZoneId); + _resourceLimitMgr.incrementResourceCount(dataDiskTemplate.getAccountId(), ResourceType.secondary_storage, dataDiskTemplate.getSize()); + } catch (Exception ex) { + s_logger.error("Failed to copy datadisk template: " + srcDataDiskTemplate.getId() + " to image store: " + dstSecStore.getName() + + " , will try copying the next one"); + } + } + } } catch (Exception ex) { s_logger.debug("failed to copy template to image store:" + dstSecStore.getName() + " ,will try next one"); } } - return false; + return true; } @@ -680,6 +701,11 @@ public VirtualMachineTemplate copyTemplate(CopyTemplateCmd cmd) throws StorageUn throw new InvalidParameterValueException("Unable to find template with id"); } + // Verify template is not Datadisk template + if (template.getTemplateType().equals(TemplateType.DATADISK)) { + throw new InvalidParameterValueException("Template " + template.getId() + " is of type Datadisk. Cannot copy Datadisk templates."); + } + DataStore srcSecStore = null; if (sourceZoneId != null) { // template is on zone-wide secondary storage diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 3d262b77d044..74bd6d029d4a 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -183,6 +183,7 @@ import com.cloud.network.security.dao.SecurityGroupVMMapDao; import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offering.ServiceOffering; @@ -2274,7 +2275,7 @@ protected boolean validPassword(String password) { public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, - Map customParametes, String customId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map customParametes, String customId, Map datadiskTemplateToDiskOfferringMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -2318,7 +2319,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, - userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId); + userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, datadiskTemplateToDiskOfferringMap); } @@ -2327,7 +2328,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, - List affinityGroupIdList, Map customParameters, String customId) throws InsufficientCapacityException, ConcurrentOperationException, + List affinityGroupIdList, Map customParameters, String customId, Map datadiskTemplateToDiskOfferringMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -2425,7 +2426,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, - userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId); + userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, datadiskTemplateToDiskOfferringMap); } @Override @@ -2433,7 +2434,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, - Map customParametrs, String customId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map customParametrs, String customId, Map datadiskTemplateToDiskOfferringMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -2517,7 +2518,7 @@ public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serv } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, httpmethod, userData, - sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId); + sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, datadiskTemplateToDiskOfferringMap); } public void checkNameForRFCCompliance(String name) { @@ -2531,7 +2532,7 @@ public void checkNameForRFCCompliance(String name) { protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate tmplt, String hostName, String displayName, Account owner, Long diskOfferingId, Long diskSize, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, String sshKeyPair, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, - List affinityGroupIdList, Map customParameters, String customId) throws InsufficientCapacityException, ResourceUnavailableException, + List affinityGroupIdList, Map customParameters, String customId, Map dataDiskTemplateToDiskOfferringMap) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException { _accountMgr.checkAccess(caller, null, owner); @@ -2602,6 +2603,38 @@ protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOf _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, (isIso || diskOfferingId == null ? 1 : 2)); _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, size); + if (dataDiskTemplateToDiskOfferringMap != null && !dataDiskTemplateToDiskOfferringMap.isEmpty()) { + for (Entry datadiskTemplateToDiskOffering : dataDiskTemplateToDiskOfferringMap.entrySet()) { + VMTemplateVO dataDiskTemplate = _templateDao.findById(datadiskTemplateToDiskOffering.getKey()); + DiskOffering dataDiskOffering = datadiskTemplateToDiskOffering.getValue(); + + if (dataDiskTemplate == null || (!dataDiskTemplate.getTemplateType().equals(TemplateType.DATADISK)) && + (dataDiskTemplate.getState().equals(VirtualMachineTemplate.State.Active))) { + throw new InvalidParameterValueException("Invalid template id specified for Datadisk template " + datadiskTemplateToDiskOffering.getKey()); + } + long dataDiskTemplateId = datadiskTemplateToDiskOffering.getKey(); + if (!dataDiskTemplate.getParentTemplateId().equals(template.getId())) { + throw new InvalidParameterValueException("Invalid Datadisk template. Specified Datadisk template " + dataDiskTemplateId + + " doesn't belong to template " + template.getId()); + } + if (dataDiskOffering == null) { + throw new InvalidParameterValueException("Invalid disk offering id " + datadiskTemplateToDiskOffering.getValue().getId() + + " specified for datadisk template " + dataDiskTemplateId); + } + if (dataDiskOffering.isCustomized()) { + throw new InvalidParameterValueException("Invalid disk offering id " + dataDiskOffering.getId() + " specified for datadisk template " + + dataDiskTemplateId + ". Custom Disk offerings are not supported for Datadisk templates"); + } + if (dataDiskOffering.getDiskSize() < dataDiskTemplate.getSize()) { + throw new InvalidParameterValueException("Invalid disk offering id " + dataDiskOffering.getId() + " specified for datadisk template " + + dataDiskTemplateId + ". Disk offering size should be greater than or equal to the template size"); + } + _templateDao.loadDetails(dataDiskTemplate); + _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, 1); + _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, dataDiskOffering.getDiskSize()); + } + } + // verify security group ids if (securityGroupIdList != null) { for (Long securityGroupId : securityGroupIdList) { @@ -2785,7 +2818,7 @@ protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOf } UserVmVO vm = commitUserVm(zone, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, hypervisor, caller, isDisplayVm, keyboard, accountId, - offering, isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters); + offering, isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dataDiskTemplateToDiskOfferringMap); // Assign instance to the group try { @@ -2845,7 +2878,7 @@ private UserVmVO commitUserVm(final DataCenter zone, final VirtualMachineTemplat final Long diskOfferingId, final Long diskSize, final String userData, final HypervisorType hypervisor, final Account caller, final Boolean isDisplayVm, final String keyboard, final long accountId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKey, final LinkedHashMap networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, - final Map customParameters) throws InsufficientCapacityException { + final Map customParameters, final Map dataDiskTemplateToDiskOfferingMap) throws InsufficientCapacityException { return Transaction.execute(new TransactionCallbackWithException() { @Override public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCapacityException { @@ -2954,7 +2987,7 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap networkNicMap, plan); } else { _orchSrvc.createVirtualMachine(vm.getUuid(), Long.toString(owner.getAccountId()), Long.toString(template.getId()), hostName, displayName, hypervisor.name(), - offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize); + offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize, dataDiskTemplateToDiskOfferingMap); } if (s_logger.isDebugEnabled()) { diff --git a/setup/db/db/schema-440to450.sql b/setup/db/db/schema-440to450.sql index 4cc4879540f8..249348235a81 100644 --- a/setup/db/db/schema-440to450.sql +++ b/setup/db/db/schema-440to450.sql @@ -223,3 +223,108 @@ CREATE VIEW `cloud`.`volume_view` AS `cloud`.`async_job` ON async_job.instance_id = volumes.id and async_job.instance_type = 'Volume' and async_job.job_status = 0; + +ALTER TABLE `cloud`.`vm_template` ADD COLUMN `parent_template_id` bigint(20) unsigned DEFAULT NULL COMMENT 'If datadisk template, then id of the root template this template belongs to'; + +DROP VIEW IF EXISTS `cloud`.`template_view`; +CREATE VIEW `cloud`.`template_view` AS + select + vm_template.id, + vm_template.uuid, + vm_template.unique_name, + vm_template.name, + vm_template.public, + vm_template.featured, + vm_template.type, + vm_template.hvm, + vm_template.bits, + vm_template.url, + vm_template.format, + vm_template.created, + vm_template.checksum, + vm_template.display_text, + vm_template.enable_password, + vm_template.dynamically_scalable, + vm_template.state template_state, + vm_template.guest_os_id, + guest_os.uuid guest_os_uuid, + guest_os.display_name guest_os_name, + vm_template.bootable, + vm_template.prepopulate, + vm_template.cross_zones, + vm_template.hypervisor_type, + vm_template.extractable, + vm_template.template_tag, + vm_template.sort_key, + vm_template.removed, + vm_template.enable_sshkey, + source_template.id source_template_id, + source_template.uuid source_template_uuid, + account.id account_id, + account.uuid account_uuid, + account.account_name account_name, + account.type account_type, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path, + projects.id project_id, + projects.uuid project_uuid, + projects.name project_name, + data_center.id data_center_id, + data_center.uuid data_center_uuid, + data_center.name data_center_name, + launch_permission.account_id lp_account_id, + parent_template.id parent_template_id, + parent_template.uuid parent_template_uuid, + template_store_ref.store_id, + image_store.scope as store_scope, + template_store_ref.state, + template_store_ref.download_state, + template_store_ref.download_pct, + template_store_ref.error_str, + template_store_ref.size, + template_store_ref.destroyed, + template_store_ref.created created_on_store, + vm_template_details.name detail_name, + vm_template_details.value detail_value, + resource_tags.id tag_id, + resource_tags.uuid tag_uuid, + resource_tags.key tag_key, + resource_tags.value tag_value, + resource_tags.domain_id tag_domain_id, + resource_tags.account_id tag_account_id, + resource_tags.resource_id tag_resource_id, + resource_tags.resource_uuid tag_resource_uuid, + resource_tags.resource_type tag_resource_type, + resource_tags.customer tag_customer, + CONCAT(vm_template.id, '_', IFNULL(data_center.id, 0)) as temp_zone_pair + from + `cloud`.`vm_template` + left join + `cloud`.`guest_os` ON guest_os.id = vm_template.guest_os_id + inner join + `cloud`.`account` ON account.id = vm_template.account_id + inner join + `cloud`.`domain` ON domain.id = account.domain_id + left join + `cloud`.`projects` ON projects.project_account_id = account.id + left join + `cloud`.`vm_template_details` ON vm_template_details.template_id = vm_template.id + left join + `cloud`.`vm_template` source_template ON source_template.id = vm_template.source_template_id + left join + `cloud`.`template_store_ref` ON template_store_ref.template_id = vm_template.id and template_store_ref.store_role = 'Image' and template_store_ref.destroyed=0 + left join + `cloud`.`image_store` ON image_store.removed is NULL AND template_store_ref.store_id is not NULL AND image_store.id = template_store_ref.store_id + left join + `cloud`.`template_zone_ref` ON template_zone_ref.template_id = vm_template.id AND template_store_ref.store_id is NULL AND template_zone_ref.removed is null + left join + `cloud`.`data_center` ON (image_store.data_center_id = data_center.id OR template_zone_ref.zone_id = data_center.id) + left join + `cloud`.`launch_permission` ON launch_permission.template_id = vm_template.id + left join + `cloud`.`vm_template` parent_template ON parent_template.id = vm_template.parent_template_id + left join + `cloud`.`resource_tags` ON resource_tags.resource_id = vm_template.id + and (resource_tags.resource_type = 'Template' or resource_tags.resource_type='ISO'); diff --git a/test/integration/component/test_ova_templates_with_multiple_disks.py b/test/integration/component/test_ova_templates_with_multiple_disks.py new file mode 100755 index 000000000000..a4011f4dde96 --- /dev/null +++ b/test/integration/component/test_ova_templates_with_multiple_disks.py @@ -0,0 +1,303 @@ +# 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. +""" P1 tests for OVA templates with multiple disks +""" +import marvin +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.lib.utils import * +from marvin.lib.base import * +from marvin.lib.common import * +import urllib +from random import random +import time +from ddt import ddt + +class Services: + """Test OVA template with mutiple disks + """ + + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, # in MHz + "memory": 128, # In MBs + }, + "disk_offering": { + "displaytext": "Small", + "name": "Small", + }, + "virtual_machine": { + "displayname": "testVM", + "hypervisor": 'VMware', + "protocol": 'TCP', + "ssh_port": 22, + "username": "root", + "password": "password", + "privateport": 22, + "publicport": 22, + }, + "template": { + "displaytext": "Template with multiple disks", + "name": "Template with multiple disks", + "isfeatured": True, + "ispublic": True, + "isextractable": False, + }, + "sleep": 60, + "timeout": 10, + "format": 'ova', + } + +@ddt +class TestOVATemplateWithMupltipleDisks(cloudstackTestCase): + + def setUp(self): + + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestOVATemplateWithMupltipleDisks, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.domain = get_domain(cls.api_client) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + # Disk offering size should be greater than datadisk template size + cls.services["disk_offering"]["disksize"] = 10 + cls.disk_offering = DiskOffering.create( + cls.api_client, + cls.services["disk_offering"] + ) + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + cls.services["account"] = cls.account.name + + cls._cleanup = [ + cls.account, + cls.service_offering, + cls.disk_offering, + ] + return + + @classmethod + def tearDownClass(cls): + try: + cls.api_client = super(TestOVATemplateWithMupltipleDisks, cls).getClsTestClient().getApiClient() + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + return + + @attr(tags = ["vmware"]) + def test_01_template_with_multiple_disks(self): + """Test template with 1 data disks + """ + # Validate the following: + # 1. Register a template in OVA format that contains 1 data disks + # 2. Verify template is in READY state + # 3. Veriy 1 additonal Datadisk Template got created + # 3. Deploy a VM from the registered template and 1 datadisk template + # 4. Verify VM is in Running state + # 5. Verify an additional data disk attached to the VM + + # Register new template + self.services["template"]["url"] = 'http://10.147.28.7/templates/single-datadisk-template.ova' + self.services["template"]["format"] = 'OVA' + self.services["template"]["ostype"] = 'CentOS 5.3 (64-bit)' + registered_template = Template.register( + self.apiclient, + self.services["template"], + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid, + hypervisor='VMware' + ) + self.debug( + "Registered a template of format: %s with id: %s" % ( + self.services["template"]["format"], + registered_template.id + )) + # Wait for template to download + registered_template.download(self.apiclient) + self.cleanup.append(registered_template) + + # Wait for template status to be changed across + time.sleep(self.services["sleep"]) + timeout = self.services["timeout"] + while True: + list_template_response = list_templates( + self.apiclient, + templatefilter='all', + id=registered_template.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + if isinstance(list_template_response, list): + break + elif timeout == 0: + raise Exception("List template failed!") + + time.sleep(5) + timeout = timeout - 1 + # Verify template response to check if template was successfully added + self.assertEqual( + isinstance(list_template_response, list), + True, + "Check for list template response return valid data" + ) + + self.assertNotEqual( + len(list_template_response), + 0, + "Check template available in List Templates" + ) + + template_response = list_template_response[0] + self.assertEqual( + template_response.isready, + True, + "Template state is not ready, it is %s" % template_response.isready + ) + + # Veriy 1 additonal Datadisk Templates got created + list_datadisk_template_response = list_templates( + self.apiclient, + templatefilter='self', + parenttemplateid=registered_template.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + + self.assertEqual( + isinstance(list_datadisk_template_response, list), + True, + "Check for datadisk list template response return valid data" + ) + + self.assertNotEqual( + len(list_datadisk_template_response), + 0, + "Check datadisk template available in List Templates" + ) + + datadisk_template_response = list_datadisk_template_response[0] + self.assertEqual( + datadisk_template_response.isready, + True, + "Datadisk template state is not ready, it is %s" % datadisk_template_response.isready + ) + + # Deploy new virtual machine using template + datadisktemplate_diskoffering_list = {datadisk_template_response.id: self.disk_offering.id} + virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=registered_template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + datadisktemplate_diskoffering_list=datadisktemplate_diskoffering_list + ) + self.debug("Creating an instance with template ID: %s" % registered_template.id) + vm_response = list_virtual_machines( + self.apiclient, + id=virtual_machine.id, + account=self.account.name, + domainid=self.account.domainid + ) + self.assertEqual( + isinstance(vm_response, list), + True, + "Check for list VMs response after VM deployment" + ) + # Verify VM response to check if VM deployment was successful + self.assertNotEqual( + len(vm_response), + 0, + "Check VMs available in List VMs response" + ) + vm = vm_response[0] + self.assertEqual( + vm.state, + 'Running', + "Check the state of VM created from Template" + ) + + # Check 1 DATA volume is attached to the VM + list_volume_response = list_volumes( + self.apiclient, + virtualmachineid=vm.id, + type='DATADISK', + listall=True + ) + self.assertNotEqual( + list_volume_response, + None, + "Check if additinal data volume is attached to VM %s " + ) + self.assertEqual( + isinstance(list_volume_response, list), + True, + "Check list volumes response for valid list" + ) + self.assertEqual( + len(list_volume_response), + 1, + "Additional DATA volume attached to the VM %s. Expected %s" % (len(list_volume_response), 1) + ) + + return diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 966e8dcbf210..7cba55d985b4 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -328,7 +328,8 @@ def create(cls, apiclient, services, templateid=None, accountid=None, securitygroupids=None, projectid=None, startvm=None, diskofferingid=None, affinitygroupnames=None, affinitygroupids=None, group=None, hostid=None, keypair=None, ipaddress=None, mode='default', method='GET',hypervisor=None, - customcpunumber=None, customcpuspeed=None, custommemory=None, rootdisksize=None): + customcpunumber=None, customcpuspeed=None, custommemory=None, rootdisksize=None, + datadisktemplate_diskoffering_list={}): """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() @@ -433,6 +434,13 @@ def create(cls, apiclient, services, templateid=None, accountid=None, if group: cmd.group = group + cmd.datadisktemplatetodiskofferinglist = [] + for datadisktemplate, diskoffering in datadisktemplate_diskoffering_list.items(): + cmd.datadisktemplatetodiskofferinglist.append({ + 'datadisktemplateid': datadisktemplate, + 'diskofferingid': diskoffering + }) + #program default access to ssh if mode.lower() == 'basic': cls.ssh_access_group(apiclient, cmd) diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java index 38b68b3c847b..5b1701997b8c 100755 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java @@ -17,6 +17,7 @@ package com.cloud.hypervisor.vmware.mo; import java.io.File; +import java.io.FileWriter; import java.net.URI; import java.net.URISyntaxException; import java.security.InvalidParameterException; @@ -24,6 +25,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.log4j.Logger; @@ -49,20 +51,29 @@ import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.MethodFault; import com.vmware.vim25.ObjectContent; +import com.vmware.vim25.OptionValue; +import com.vmware.vim25.OvfCreateDescriptorParams; +import com.vmware.vim25.OvfCreateDescriptorResult; import com.vmware.vim25.OvfCreateImportSpecParams; import com.vmware.vim25.OvfCreateImportSpecResult; +import com.vmware.vim25.OvfFile; import com.vmware.vim25.OvfFileItem; import com.vmware.vim25.VMwareDVSConfigSpec; import com.vmware.vim25.VMwareDVSPortSetting; import com.vmware.vim25.VMwareDVSPvlanConfigSpec; import com.vmware.vim25.VMwareDVSPvlanMapEntry; +import com.vmware.vim25.VirtualDevice; import com.vmware.vim25.VirtualDeviceConfigSpec; import com.vmware.vim25.VirtualDeviceConfigSpecOperation; +import com.vmware.vim25.VirtualDisk; +import com.vmware.vim25.VirtualIDEController; import com.vmware.vim25.VirtualLsiLogicController; import com.vmware.vim25.VirtualMachineConfigSpec; import com.vmware.vim25.VirtualMachineFileInfo; import com.vmware.vim25.VirtualMachineGuestOsIdentifier; +import com.vmware.vim25.VirtualMachineImportSpec; import com.vmware.vim25.VirtualMachineVideoCard; +import com.vmware.vim25.VirtualSCSIController; import com.vmware.vim25.VirtualSCSISharing; import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec; import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec; @@ -92,6 +103,7 @@ public class HypervisorHostHelper { // make vmware-base loosely coupled with cloud-specific stuff, duplicate VLAN.UNTAGGED constant here private static final String UNTAGGED_VLAN_NAME = "untagged"; + private static final String OVA_OPTION_KEY_BOOTDISK = "cloud.ova.bootdisk"; public static VirtualMachineMO findVmFromObjectContent(VmwareContext context, ObjectContent[] ocs, String name, String instanceNameCustomField) { @@ -128,6 +140,10 @@ public static ManagedObjectReference findDatastoreWithBackwardsCompatibility(Vmw return morDs; } + public static String getSecondaryDatastoreUUID(String storeUrl) { + return UUID.nameUUIDFromBytes(storeUrl.getBytes()).toString(); + } + public static DatastoreMO getHyperHostDatastoreMO(VmwareHypervisorHost hyperHost, String datastoreName) throws Exception { ObjectContent[] ocs = hyperHost.getDatastorePropertiesOnHyperHost(new String[] {"name"}); if (ocs != null && ocs.length > 0) { @@ -1174,6 +1190,198 @@ public static boolean createBlankVm(VmwareHypervisorHost host, String vmName, St return false; } + public static List> readOVF(VmwareHypervisorHost host, String ovfFilePath, DatastoreMO dsMo) throws Exception { + List> ovfVolumeInfos = new ArrayList>(); + List files = new ArrayList(); + + ManagedObjectReference morRp = host.getHyperHostOwnerResourcePool(); + assert (morRp != null); + ManagedObjectReference morHost = host.getMor(); + + String importEntityName = UUID.randomUUID().toString(); + OvfCreateImportSpecParams importSpecParams = new OvfCreateImportSpecParams(); + importSpecParams.setHostSystem(morHost); + importSpecParams.setLocale("US"); + importSpecParams.setEntityName(importEntityName); + importSpecParams.setDeploymentOption(""); + + String ovfDescriptor = HttpNfcLeaseMO.readOvfContent(ovfFilePath); + VmwareContext context = host.getContext(); + OvfCreateImportSpecResult ovfImportResult = + context.getService().createImportSpec(context.getServiceContent().getOvfManager(), ovfDescriptor, morRp, dsMo.getMor(), importSpecParams); + + if (ovfImportResult == null) { + String msg = "createImportSpec() failed. ovfFilePath: " + ovfFilePath; + s_logger.error(msg); + throw new Exception(msg); + } + + if (!ovfImportResult.getError().isEmpty()) { + for (LocalizedMethodFault fault : ovfImportResult.getError()) { + s_logger.error("createImportSpec error: " + fault.getLocalizedMessage()); + } + throw new CloudException("Failed to create an import spec from " + ovfFilePath + ". Check log for details."); + } + + if (!ovfImportResult.getWarning().isEmpty()) { + for (LocalizedMethodFault fault : ovfImportResult.getError()) { + s_logger.warn("createImportSpec warning: " + fault.getLocalizedMessage()); + } + } + + VirtualMachineImportSpec importSpec = (VirtualMachineImportSpec)ovfImportResult.getImportSpec(); + if (importSpec == null) { + String msg = "createImportSpec() failed to create import specification for OVF template at " + ovfFilePath; + s_logger.error(msg); + throw new Exception(msg); + } + + File ovfFile = new File(ovfFilePath); + for (OvfFileItem ovfFileItem : ovfImportResult.getFileItem()) { + String absFile = ovfFile.getParent() + File.separator + ovfFileItem.getPath(); + files.add(absFile); + } + + int osDiskSeqNumber = 0; + VirtualMachineConfigSpec config = importSpec.getConfigSpec(); + String paramVal = getOVFParamValue(config); + if (paramVal != null && !paramVal.isEmpty()) { + try { + osDiskSeqNumber = getOsDiskFromOvfConf(config, paramVal); + } catch (Exception e) { + osDiskSeqNumber = 0; + } + } + + int diskCount = 0; + int deviceCount = 0; + List deviceConfigList = config.getDeviceChange(); + for (VirtualDeviceConfigSpec deviceSpec : deviceConfigList) { + Boolean osDisk = false; + VirtualDevice device = deviceSpec.getDevice(); + if (device instanceof VirtualDisk) { + if ((osDiskSeqNumber == 0 && diskCount == 0) || osDiskSeqNumber == deviceCount) { + osDisk = true; + } + Pair ovfVolumeInfo = new Pair(files.get(diskCount), osDisk); + ovfVolumeInfos.add(ovfVolumeInfo); + diskCount++; + } + deviceCount++; + } + return ovfVolumeInfos; + } + + public static int getOsDiskFromOvfConf(VirtualMachineConfigSpec config, String deviceLocation) { + List deviceConfigList = config.getDeviceChange(); + int controllerKey = 0; + int deviceSeqNumber = 0; + int controllerNumber = 0; + int deviceNodeNumber = 0; + int controllerCount = 0; + String[] virtualNodeInfo = deviceLocation.split(":"); + if (deviceLocation.startsWith("scsi")) { + + controllerNumber = Integer.parseInt(virtualNodeInfo[0].substring(4)); // get substring excluding prefix scsi + deviceNodeNumber = Integer.parseInt(virtualNodeInfo[1]); + + for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { + VirtualDevice device = deviceConfig.getDevice(); + if (device instanceof VirtualSCSIController) { + if (controllerNumber == controllerCount) { //((VirtualSCSIController)device).getBusNumber()) { + controllerKey = device.getKey(); + break; + } + controllerCount++; + } + } + } else { + controllerNumber = Integer.parseInt(virtualNodeInfo[0].substring(3)); // get substring excluding prefix ide + deviceNodeNumber = Integer.parseInt(virtualNodeInfo[1]); + controllerCount = 0; + + for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { + VirtualDevice device = deviceConfig.getDevice(); + if (device instanceof VirtualIDEController) { + if (controllerNumber == controllerCount) { //((VirtualIDEController)device).getBusNumber()) { + // Only 2 IDE controllers supported and they will have bus numbers 0 and 1 + controllerKey = device.getKey(); + break; + } + controllerCount++; + } + } + } + // Get devices on this controller at specific device node. + for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { + VirtualDevice device = deviceConfig.getDevice(); + if (device instanceof VirtualDisk) { + if (controllerKey == device.getControllerKey() && deviceNodeNumber == device.getUnitNumber()) { + break; + } + deviceSeqNumber++; + } + } + return deviceSeqNumber; + } + + public static String getOVFParamValue(VirtualMachineConfigSpec config) { + String paramVal = ""; + List options = config.getExtraConfig(); + for (OptionValue option : options) { + if (OVA_OPTION_KEY_BOOTDISK.equalsIgnoreCase(option.getKey())) { + paramVal = (String)option.getValue(); + break; + } + } + return paramVal; + } + + public static void createOvfFile(VmwareHypervisorHost host, String diskFileName, String ovfName, String datastorePath, String templatePath, long diskCapacity, long fileSize, + ManagedObjectReference morDs) throws Exception { + VmwareContext context = host.getContext(); + ManagedObjectReference morOvf = context.getServiceContent().getOvfManager(); + VirtualMachineMO workerVmMo = HypervisorHostHelper.createWorkerVM(host, new DatastoreMO(context, morDs), ovfName); + if (workerVmMo == null) + throw new Exception("Unable to find just-created worker VM"); + + String[] disks = {datastorePath + File.separator + diskFileName}; + try { + VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec(); + VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec(); + + // Reconfigure worker VM with datadisk + VirtualDevice device = VmwareHelper.prepareDiskDevice(workerVmMo, null, -1, disks, morDs, -1, 1); + deviceConfigSpec.setDevice(device); + deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); + vmConfigSpec.getDeviceChange().add(deviceConfigSpec); + workerVmMo.configureVm(vmConfigSpec); + + // Write OVF descriptor file + OvfCreateDescriptorParams ovfDescParams = new OvfCreateDescriptorParams(); + String deviceId = File.separator + workerVmMo.getMor().getValue() + File.separator + "VirtualIDEController0:0"; + OvfFile ovfFile = new OvfFile(); + ovfFile.setPath(diskFileName); + ovfFile.setDeviceId(deviceId); + ovfFile.setSize(fileSize); + ovfFile.setCapacity(diskCapacity); + ovfDescParams.getOvfFiles().add(ovfFile); + OvfCreateDescriptorResult ovfCreateDescriptorResult = context.getService().createDescriptor(morOvf, workerVmMo.getMor(), ovfDescParams); + + String ovfPath = templatePath + File.separator + ovfName + ".ovf"; + try { + FileWriter out = new FileWriter(ovfPath); + out.write(ovfCreateDescriptorResult.getOvfDescriptor()); + out.close(); + } catch (Exception e) { + throw e; + } + } finally { + workerVmMo.detachAllDisks(); + workerVmMo.destroy(); + } + } + public static VirtualMachineMO createWorkerVM(VmwareHypervisorHost hyperHost, DatastoreMO dsMo, String vmName) throws Exception { // Allow worker VM to float within cluster so that we will have better chance to