diff --git a/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java b/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java index 76d6d952814..d43625c09a9 100644 --- a/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java @@ -19,6 +19,9 @@ package com.cloud.agent.api.to; import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.PortForwardingRule; import com.cloud.utils.net.NetUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; /** * PortForwardingRuleTO specifies one port forwarding rule. @@ -29,6 +32,8 @@ public class PortForwardingRuleTO extends FirewallRuleTO { String dstIp; int[] dstPortRange; + List sourceCidrList; + protected PortForwardingRuleTO() { super(); } @@ -37,6 +42,7 @@ public class PortForwardingRuleTO extends FirewallRuleTO { super(rule, srcVlanTag, srcIp); this.dstIp = rule.getDestinationIpAddress().addr(); this.dstPortRange = new int[] {rule.getDestinationPortStart(), rule.getDestinationPortEnd()}; + this.sourceCidrList = rule.getSourceCidrList(); } public PortForwardingRuleTO(long id, String srcIp, int srcPortStart, int srcPortEnd, String dstIp, int dstPortStart, int dstPortEnd, String protocol, @@ -58,4 +64,11 @@ public class PortForwardingRuleTO extends FirewallRuleTO { return NetUtils.portRangeToString(dstPortRange); } + public String getSourceCidrListAsString() { + if (sourceCidrList != null) { + return StringUtils.join(sourceCidrList, ","); + } + return null; + } + } diff --git a/api/src/main/java/com/cloud/network/rules/RulesService.java b/api/src/main/java/com/cloud/network/rules/RulesService.java index 0b4afeef945..547d4ab51e0 100644 --- a/api/src/main/java/com/cloud/network/rules/RulesService.java +++ b/api/src/main/java/com/cloud/network/rules/RulesService.java @@ -26,6 +26,7 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; import com.cloud.utils.Pair; import com.cloud.utils.net.Ip; +import org.apache.cloudstack.api.command.user.firewall.UpdatePortForwardingRuleCmd; public interface RulesService { Pair, Integer> searchStaticNatRules(Long ipId, Long id, Long vmId, Long start, Long size, String accountName, Long domainId, @@ -81,6 +82,8 @@ public interface RulesService { boolean disableStaticNat(long ipId) throws ResourceUnavailableException, NetworkRuleConflictException, InsufficientAddressCapacityException; - PortForwardingRule updatePortForwardingRule(long id, Integer privatePort, Integer privateEndPort, Long virtualMachineId, Ip vmGuestIp, String customId, Boolean forDisplay); + PortForwardingRule updatePortForwardingRule(UpdatePortForwardingRuleCmd cmd); + + void validatePortForwardingSourceCidrList(List sourceCidrList); } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 8f78fe5c4b4..bf8b79b29d0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -244,6 +244,7 @@ public class ApiConstants { public static final String ICMP_TYPE = "icmptype"; public static final String ID = "id"; public static final String IDS = "ids"; + public static final String IMPORT_INSTANCE_HOST_ID = "importinstancehostid"; public static final String INDEX = "index"; public static final String INSTANCES_DISKS_STATS_RETENTION_ENABLED = "instancesdisksstatsretentionenabled"; public static final String INSTANCES_DISKS_STATS_RETENTION_TIME = "instancesdisksstatsretentiontime"; @@ -464,6 +465,7 @@ public class ApiConstants { public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid"; public static final String SNAPSHOT_TYPE = "snapshottype"; public static final String SNAPSHOT_QUIESCEVM = "quiescevm"; + public static final String SOURCE_CIDR_LIST = "sourcecidrlist"; public static final String SOURCE_ZONE_ID = "sourcezoneid"; public static final String SSL_VERIFICATION = "sslverification"; public static final String START_ASN = "startasn"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java index 6f148ff0ee4..db43b53ab9a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java @@ -144,15 +144,19 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd { private String clusterName; @Parameter(name = ApiConstants.CONVERT_INSTANCE_HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, - description = "(only for importing VMs from VMware to KVM) optional - the host to perform the virt-v2v migration from VMware to KVM.") + description = "(only for importing VMs from VMware to KVM) optional - the host to perform the virt-v2v conversion from VMware to KVM.") private Long convertInstanceHostId; + @Parameter(name = ApiConstants.IMPORT_INSTANCE_HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, since = "4.19.2", + description = "(only for importing VMs from VMware to KVM) optional - the host to import the converted instance from VMware to KVM.") + private Long importInstanceHostId; + @Parameter(name = ApiConstants.CONVERT_INSTANCE_STORAGE_POOL_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "(only for importing VMs from VMware to KVM) optional - the temporary storage pool to perform the virt-v2v migration from VMware to KVM.") private Long convertStoragePoolId; @Parameter(name = ApiConstants.FORCE_MS_TO_IMPORT_VM_FILES, type = CommandType.BOOLEAN, - description = "(only for importing VMs from VMware to KVM) optional - if true, forces MS to import VM file(s) to temporary storage, else uses KVM Host if ovftool is available, falls back to MS if not.") + description = "(only for importing VMs from VMware to KVM) optional - if true, forces MS to export OVF from VMware to temporary storage, else uses KVM Host if ovftool is available, falls back to MS if not.") private Boolean forceMsToImportVmFiles; ///////////////////////////////////////////////////// @@ -199,6 +203,10 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd { return convertInstanceHostId; } + public Long getImportInstanceHostId() { + return importInstanceHostId; + } + public Long getConvertStoragePoolId() { return convertStoragePoolId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java index 3545b3d21fb..9d1fe176019 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java @@ -105,8 +105,12 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P description = "the ID of the virtual machine for the port forwarding rule") private Long virtualMachineId; - @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "the cidr list to forward traffic from. Multiple entries must be separated by a single comma character (,). This parameter is deprecated. Do not use.") - private List cidrlist; + @Parameter(name = ApiConstants.CIDR_LIST, + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = " the source CIDR list to allow traffic from; all other CIDRs will be blocked. " + + "Multiple entries must be separated by a single comma character (,). This param will be used only for VPC tiers. By default, all CIDRs are allowed.") + private List sourceCidrList; @Parameter(name = ApiConstants.OPEN_FIREWALL, type = CommandType.BOOLEAN, description = "if true, firewall rule for source/end public port is automatically created; " + "if false - firewall rule has to be created explicitly. If not specified 1) defaulted to false when PF" @@ -155,11 +159,7 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P @Override public List getSourceCidrList() { - if (cidrlist != null) { - throw new InvalidParameterValueException("Parameter cidrList is deprecated; if you need to open firewall " - + "rule for the specific cidr, please refer to createFirewallRule command"); - } - return null; + return sourceCidrList; } public Boolean getOpenFirewall() { @@ -332,12 +332,6 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P @Override public void create() { - // cidr list parameter is deprecated - if (cidrlist != null) { - throw new InvalidParameterValueException( - "Parameter cidrList is deprecated; if you need to open firewall rule for the specific cidr, please refer to createFirewallRule command"); - } - Ip privateIp = getVmSecondaryIp(); if (privateIp != null) { if (!NetUtils.isValidIp4(privateIp.toString())) { @@ -345,6 +339,8 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P } } + _rulesService.validatePortForwardingSourceCidrList(sourceCidrList); + try { PortForwardingRule result = _rulesService.createPortForwardingRule(this, virtualMachineId, privateIp, getOpenFirewall(), isDisplay()); setEntityId(result.getId()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java index 3fb66bd861f..ee4bd18ad40 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java @@ -32,6 +32,8 @@ import com.cloud.network.rules.PortForwardingRule; import com.cloud.user.Account; import com.cloud.utils.net.Ip; +import java.util.List; + @APICommand(name = "updatePortForwardingRule", responseObject = FirewallRuleResponse.class, description = "Updates a port forwarding rule. Only the private port and the virtual machine can be updated.", entityType = {PortForwardingRule.class}, @@ -63,6 +65,13 @@ public class UpdatePortForwardingRuleCmd extends BaseAsyncCustomIdCmd { @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the rule to the end user or not", since = "4.4", authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name = ApiConstants.CIDR_LIST, + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = " the source CIDR list to allow traffic from; all other CIDRs will be blocked. " + + "Multiple entries must be separated by a single comma character (,). This param will be used only for VPC tiers. By default, all CIDRs are allowed.") + private List sourceCidrList; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -94,6 +103,10 @@ public class UpdatePortForwardingRuleCmd extends BaseAsyncCustomIdCmd { return display; } + public List getSourceCidrList() { + return sourceCidrList; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -130,7 +143,7 @@ public class UpdatePortForwardingRuleCmd extends BaseAsyncCustomIdCmd { @Override public void execute() { - PortForwardingRule rule = _rulesService.updatePortForwardingRule(getId(), getPrivatePort(), getPrivateEndPort(), getVirtualMachineId(), getVmGuestIp(), getCustomId(), getDisplay()); + PortForwardingRule rule = _rulesService.updatePortForwardingRule(this); FirewallRuleResponse fwResponse = new FirewallRuleResponse(); if (rule != null) { fwResponse = _responseGenerator.createPortForwardingRuleResponse(rule); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java index f86d1ae85da..34798c4efe1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java @@ -104,7 +104,7 @@ public class CreateLoadBalancerRuleCmd extends BaseAsyncCreateCmd /*implements L @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the domain ID associated with the load balancer") private Long domainId; - @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, since = "4.18.0.0", description = "the CIDR list to allow traffic, " + @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, since = "4.18.0.0", description = "the source CIDR list to allow traffic from; " + "all other CIDRs will be blocked. Multiple entries must be separated by a single comma character (,). By default, all CIDRs are allowed.") private List cidrlist; diff --git a/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.java b/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.java index 829888570a6..174348f4f18 100644 --- a/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.java @@ -20,6 +20,8 @@ import org.apache.cloudstack.vm.UnmanagedInstanceTO; public class ConvertInstanceAnswer extends Answer { + private String temporaryConvertUuid; + public ConvertInstanceAnswer() { super(); } @@ -34,6 +36,15 @@ public class ConvertInstanceAnswer extends Answer { this.convertedInstance = convertedInstance; } + public ConvertInstanceAnswer(Command command, String temporaryConvertUuid) { + super(command, true, ""); + this.temporaryConvertUuid = temporaryConvertUuid; + } + + public String getTemporaryConvertUuid() { + return temporaryConvertUuid; + } + public UnmanagedInstanceTO getConvertedInstance() { return convertedInstance; } diff --git a/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceAnswer.java b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceAnswer.java new file mode 100644 index 00000000000..2a8f8704e3f --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceAnswer.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; + +import org.apache.cloudstack.vm.UnmanagedInstanceTO; + +public class ImportConvertedInstanceAnswer extends Answer { + + public ImportConvertedInstanceAnswer() { + super(); + } + private UnmanagedInstanceTO convertedInstance; + + public ImportConvertedInstanceAnswer(Command command, boolean success, String details) { + super(command, success, details); + } + + public ImportConvertedInstanceAnswer(Command command, UnmanagedInstanceTO convertedInstance) { + super(command, true, ""); + this.convertedInstance = convertedInstance; + } + + public UnmanagedInstanceTO getConvertedInstance() { + return convertedInstance; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java new file mode 100644 index 00000000000..9d50e852ced --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api; + +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.RemoteInstanceTO; + +import java.util.List; + +public class ImportConvertedInstanceCommand extends Command { + + private RemoteInstanceTO sourceInstance; + private List destinationStoragePools; + private DataStoreTO conversionTemporaryLocation; + private String temporaryConvertUuid; + + public ImportConvertedInstanceCommand() { + } + + public ImportConvertedInstanceCommand(RemoteInstanceTO sourceInstance, + List destinationStoragePools, + DataStoreTO conversionTemporaryLocation, String temporaryConvertUuid) { + this.sourceInstance = sourceInstance; + this.destinationStoragePools = destinationStoragePools; + this.conversionTemporaryLocation = conversionTemporaryLocation; + this.temporaryConvertUuid = temporaryConvertUuid; + } + + public RemoteInstanceTO getSourceInstance() { + return sourceInstance; + } + + public List getDestinationStoragePools() { + return destinationStoragePools; + } + + public DataStoreTO getConversionTemporaryLocation() { + return conversionTemporaryLocation; + } + + public String getTemporaryConvertUuid() { + return temporaryConvertUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java index c9d0e74e2e4..4daef64ed8a 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java @@ -41,7 +41,7 @@ public class SetPortForwardingRulesConfigItem extends AbstractConfigItemFacade { for (final PortForwardingRuleTO rule : command.getRules()) { final ForwardingRule fwdRule = new ForwardingRule(rule.revoked(), rule.getProtocol().toLowerCase(), rule.getSrcIp(), rule.getStringSrcPortRange(), rule.getDstIp(), - rule.getStringDstPortRange()); + rule.getStringDstPortRange(), rule.getSourceCidrListAsString()); rules.add(fwdRule); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java index cf3e43d1c01..1dc1cc855c4 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java @@ -26,18 +26,21 @@ public class ForwardingRule { private String sourcePortRange; private String destinationIpAddress; private String destinationPortRange; + private String sourceCidrList; public ForwardingRule() { // Empty constructor for (de)serialization } - public ForwardingRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress, String destinationPortRange) { + public ForwardingRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress, String destinationPortRange, + String sourceCidrList) { this.revoke = revoke; this.protocol = protocol; this.sourceIpAddress = sourceIpAddress; this.sourcePortRange = sourcePortRange; this.destinationIpAddress = destinationIpAddress; this.destinationPortRange = destinationPortRange; + this.sourceCidrList = sourceCidrList; } public boolean isRevoke() { @@ -88,4 +91,8 @@ public class ForwardingRule { this.destinationPortRange = destinationPortRange; } + public String getSourceCidrList() { + return sourceCidrList; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java index 55c454860ef..df5b6b5d647 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java @@ -29,4 +29,6 @@ public interface FirewallRulesCidrsDao extends GenericDao listByFirewallRuleId(long firewallRuleId); + + void updateSourceCidrsForRule(Long firewallRuleId, List sourceCidrList); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java index fdd1e0ec43a..6279289bdfe 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.DB; @@ -45,7 +46,7 @@ public class FirewallRulesCidrsDaoImpl extends GenericDaoBase results = search(sc, null); - List cidrs = new ArrayList(results.size()); + List cidrs = new ArrayList<>(results.size()); for (FirewallRulesCidrsVO result : results) { cidrs.add(result.getCidr()); } @@ -63,9 +64,27 @@ public class FirewallRulesCidrsDaoImpl extends GenericDaoBase sourceCidrList) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + + SearchCriteria sc = CidrsSearch.create(); + sc.setParameters("firewallRuleId", firewallRuleId); + remove(sc); + + persist(firewallRuleId, sourceCidrList); + + txn.commit(); + } + @Override @DB public void persist(long firewallRuleId, List sourceCidrs) { + if (CollectionUtils.isEmpty(sourceCidrs)) { + return; + } + TransactionLegacy txn = TransactionLegacy.currentTxn(); txn.start(); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java index 1698e0ed2da..c8bd7e2147e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java @@ -251,9 +251,6 @@ public class FirewallRulesDaoImpl extends GenericDaoBase i } public void saveSourceCidrs(FirewallRuleVO firewallRule, List cidrList) { - if (cidrList == null) { - return; - } _firewallRulesCidrsDao.persist(firewallRule.getId(), cidrList); } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java index e1a698881f3..576e2f8172e 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java @@ -25,6 +25,7 @@ import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; +import javax.persistence.Transient; import com.cloud.utils.net.Ip; @@ -47,21 +48,30 @@ public class PortForwardingRuleVO extends FirewallRuleVO implements PortForwardi @Column(name = "instance_id") private long virtualMachineId; + @Transient + List sourceCidrs; + public PortForwardingRuleVO() { } public PortForwardingRuleVO(String xId, long srcIpId, int srcPortStart, int srcPortEnd, Ip dstIp, int dstPortStart, int dstPortEnd, String protocol, long networkId, - long accountId, long domainId, long instanceId) { - super(xId, srcIpId, srcPortStart, srcPortEnd, protocol, networkId, accountId, domainId, Purpose.PortForwarding, null, null, null, null, null); + long accountId, long domainId, long instanceId, List sourceCidrs) { + super(xId, srcIpId, srcPortStart, srcPortEnd, protocol, networkId, accountId, domainId, Purpose.PortForwarding, sourceCidrs, null, null, null, null); this.destinationIpAddress = dstIp; this.virtualMachineId = instanceId; this.destinationPortStart = dstPortStart; this.destinationPortEnd = dstPortEnd; + this.sourceCidrs = sourceCidrs; } - public PortForwardingRuleVO(String xId, long srcIpId, int srcPort, Ip dstIp, int dstPort, String protocol, List sourceCidrs, long networkId, long accountId, - long domainId, long instanceId) { - this(xId, srcIpId, srcPort, srcPort, dstIp, dstPort, dstPort, protocol.toLowerCase(), networkId, accountId, domainId, instanceId); + public PortForwardingRuleVO(String xId, long srcIpId, int srcPortStart, int srcPortEnd, Ip dstIp, int dstPortStart, int dstPortEnd, String protocol, long networkId, + long accountId, long domainId, long instanceId) { + this(xId, srcIpId, srcPortStart, srcPortEnd, dstIp, dstPortStart, dstPortEnd, protocol.toLowerCase(), networkId, accountId, domainId, instanceId, null); + } + + public PortForwardingRuleVO(String xId, long srcIpId, int srcPort, Ip dstIp, int dstPort, String protocol, long networkId, long accountId, + long domainId, long instanceId) { + this(xId, srcIpId, srcPort, srcPort, dstIp, dstPort, dstPort, protocol.toLowerCase(), networkId, accountId, domainId, instanceId, null); } @Override @@ -106,4 +116,13 @@ public class PortForwardingRuleVO extends FirewallRuleVO implements PortForwardi return null; } + public void setSourceCidrList(List sourceCidrs) { + this.sourceCidrs = sourceCidrs; + } + + @Override + public List getSourceCidrList() { + return sourceCidrs; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java index 3a404b3f2df..1b3df06e1a2 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java @@ -31,6 +31,9 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionLegacy; @Component public class PortForwardingRulesDaoImpl extends GenericDaoBase implements PortForwardingRulesDao { @@ -42,7 +45,7 @@ public class PortForwardingRulesDaoImpl extends GenericDaoBase ActiveRulesSearchByAccount; @Inject - protected FirewallRulesCidrsDao _portForwardingRulesCidrsDao; + protected FirewallRulesCidrsDao portForwardingRulesCidrsDao; protected PortForwardingRulesDaoImpl() { super(); @@ -183,4 +186,43 @@ public class PortForwardingRulesDaoImpl extends GenericDaoBase) transactionStatus -> { + PortForwardingRuleVO dbPfRule = super.persist(portForwardingRule); + + portForwardingRulesCidrsDao.persist(portForwardingRule.getId(), portForwardingRule.getSourceCidrList()); + List cidrList = portForwardingRulesCidrsDao.getSourceCidrs(portForwardingRule.getId()); + portForwardingRule.setSourceCidrList(cidrList); + + return dbPfRule; + }); + + } + + @Override + public boolean update(Long id, PortForwardingRuleVO entity) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + + boolean success = super.update(id, entity); + if (!success) { + return false; + } + + portForwardingRulesCidrsDao.updateSourceCidrsForRule(entity.getId(), entity.getSourceCidrList()); + txn.commit(); + + return true; + } + + @Override + public PortForwardingRuleVO findById(Long id) { + PortForwardingRuleVO rule = super.findById(id); + + List sourceCidrList = portForwardingRulesCidrsDao.getSourceCidrs(id); + rule.setSourceCidrList(sourceCidrList); + + return rule; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java index c105acf40b8..3e6999fad36 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java @@ -50,6 +50,9 @@ public class VolumeVO implements Volume { @Column(name = "id") long id; + @Column(name = "last_id") + private long lastId; + @Column(name = "name") String name; @@ -690,4 +693,12 @@ public class VolumeVO implements Volume { public void setDeleteProtection(boolean deleteProtection) { this.deleteProtection = deleteProtection; } + + public long getLastId() { + return lastId; + } + + public void setLastId(long lastId) { + this.lastId = lastId; + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql index 0cb10f4a0ef..028c3f619db 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql @@ -70,3 +70,6 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user_data', 'removed', 'datetime COM UPDATE `cloud`.`configuration` SET `options` = 'FirstFitRouting,RandomAllocator,TestingAllocator,FirstFitAllocator,RecreateHostAllocator' WHERE `name` = 'host.allocators.order'; + +-- Add last_id to the volumes table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('volumes','last_id', 'bigint(20) unsigned DEFAULT NULL'); diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index 9755dcbb0df..3456731ef1c 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -2293,6 +2293,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { if (success) { VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId()); volumeVO.setFormat(ImageFormat.QCOW2); + volumeVO.setLastId(srcVolumeInfo.getId()); + _volumeDao.update(volumeVO.getId(), volumeVO); _volumeService.copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(Event.OperationSuccessed, null, srcVolumeInfo, destVolumeInfo, false); diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 9a3319f79a3..3ca1d9201db 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -1720,6 +1720,7 @@ public class VolumeServiceImpl implements VolumeService { newVol.setPassphraseId(volume.getPassphraseId()); newVol.setEncryptFormat(volume.getEncryptFormat()); } + newVol.setLastId(volume.getId()); return volDao.persist(newVol); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 0b0416a8049..7374d46edd6 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -3000,6 +3000,17 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return dataPath; } + public static boolean useBLOCKDiskType(KVMPhysicalDisk physicalDisk) { + return physicalDisk != null && + physicalDisk.getPool().getType() == StoragePoolType.Linstor && + physicalDisk.getFormat() != null && + physicalDisk.getFormat()== PhysicalDiskFormat.RAW; + } + + public static DiskDef.DiskType getDiskType(KVMPhysicalDisk physicalDisk) { + return useBLOCKDiskType(physicalDisk) ? DiskDef.DiskType.BLOCK : DiskDef.DiskType.FILE; + } + public void createVbd(final Connect conn, final VirtualMachineTO vmSpec, final String vmName, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException, URISyntaxException { final Map details = vmSpec.getDetails(); final List disks = Arrays.asList(vmSpec.getDisks()); @@ -3045,7 +3056,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv physicalDisk = getPhysicalDiskFromNfsStore(dataStoreUrl, data); } else if (primaryDataStoreTO.getPoolType().equals(StoragePoolType.SharedMountPoint) || primaryDataStoreTO.getPoolType().equals(StoragePoolType.Filesystem) || - primaryDataStoreTO.getPoolType().equals(StoragePoolType.StorPool)) { + primaryDataStoreTO.getPoolType().equals(StoragePoolType.StorPool) || + primaryDataStoreTO.getPoolType().equals(StoragePoolType.Linstor)) { physicalDisk = getPhysicalDiskPrimaryStore(primaryDataStoreTO, data); } } @@ -3095,8 +3107,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final DiskDef disk = new DiskDef(); int devId = volume.getDiskSeq().intValue(); if (volume.getType() == Volume.Type.ISO) { - - disk.defISODisk(volPath, devId, isUefiEnabled); + final DiskDef.DiskType diskType = getDiskType(physicalDisk); + disk.defISODisk(volPath, devId, isUefiEnabled, diskType); if (guestCpuArch != null && guestCpuArch.equals("aarch64")) { disk.setBusType(DiskDef.DiskBus.SCSI); @@ -3195,7 +3207,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv if (vmSpec.getType() != VirtualMachine.Type.User) { final DiskDef iso = new DiskDef(); - iso.defISODisk(sysvmISOPath); + iso.defISODisk(sysvmISOPath, DiskDef.DiskType.FILE); if (guestCpuArch != null && guestCpuArch.equals("aarch64")) { iso.setBusType(DiskDef.DiskBus.SCSI); } @@ -3408,7 +3420,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv List disks = getDisks(conn, vmName); DiskDef configdrive = null; for (DiskDef disk : disks) { - if (disk.getDeviceType() == DiskDef.DeviceType.CDROM && disk.getDiskLabel() == CONFIG_DRIVE_ISO_DISK_LABEL) { + if (disk.getDeviceType() == DiskDef.DeviceType.CDROM && CONFIG_DRIVE_ISO_DISK_LABEL.equals(disk.getDiskLabel())) { configdrive = disk; } } @@ -3438,11 +3450,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final String name = isoPath.substring(index + 1); final KVMStoragePool secondaryPool = storagePoolManager.getStoragePoolByURI(path); final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name); + final DiskDef.DiskType diskType = getDiskType(isoVol); isoPath = isoVol.getPath(); - iso.defISODisk(isoPath, diskSeq); + iso.defISODisk(isoPath, diskSeq, diskType); } else { - iso.defISODisk(null, diskSeq); + iso.defISODisk(null, diskSeq, DiskDef.DiskType.FILE); } final String result = attachOrDetachDevice(conn, true, vmName, iso.toString()); @@ -3450,7 +3463,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final List disks = getDisks(conn, vmName); for (final DiskDef disk : disks) { if (disk.getDeviceType() == DiskDef.DeviceType.CDROM - && (diskSeq == null || disk.getDiskLabel() == iso.getDiskLabel())) { + && (diskSeq == null || disk.getDiskLabel().equals(iso.getDiskLabel()))) { cleanupDisk(disk); } } @@ -4046,7 +4059,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return stopVMInternal(conn, vmName, true); } String ret = stopVMInternal(conn, vmName, false); - if (ret == Script.ERR_TIMEOUT) { + if (Script.ERR_TIMEOUT.equals(ret)) { ret = stopVMInternal(conn, vmName, true); } else if (ret != null) { /* diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index 46620e77fda..c4d5ac48f6f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -127,11 +127,10 @@ public class LibvirtDomainXMLParser { } def.defFileBasedDisk(diskFile, diskLabel, DiskDef.DiskBus.valueOf(bus.toUpperCase()), fmt); } else if (device.equalsIgnoreCase("cdrom")) { - def.defISODisk(diskFile, i+1, diskLabel); + def.defISODisk(diskFile, i+1, diskLabel, DiskDef.DiskType.FILE); } } else if (type.equalsIgnoreCase("block")) { - def.defBlockBasedDisk(diskDev, diskLabel, - DiskDef.DiskBus.valueOf(bus.toUpperCase())); + parseDiskBlock(def, device, diskDev, diskLabel, bus, diskFile, i); } if (StringUtils.isNotBlank(diskCacheMode)) { def.setCacheMode(DiskDef.DiskCacheMode.valueOf(diskCacheMode.toUpperCase())); @@ -450,6 +449,25 @@ public class LibvirtDomainXMLParser { return node.getAttribute(attr); } + /** + * Parse the disk block part of the libvirt XML. + * @param def + * @param device + * @param diskDev + * @param diskLabel + * @param bus + * @param diskFile + * @param curDiskIndex + */ + private void parseDiskBlock(DiskDef def, String device, String diskDev, String diskLabel, String bus, + String diskFile, int curDiskIndex) { + if (device.equalsIgnoreCase("disk")) { + def.defBlockBasedDisk(diskDev, diskLabel, DiskDef.DiskBus.valueOf(bus.toUpperCase())); + } else if (device.equalsIgnoreCase("cdrom")) { + def.defISODisk(diskFile, curDiskIndex+1, diskLabel, DiskDef.DiskType.BLOCK); + } + } + public Integer getVncPort() { return vncPort; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index 0f11c12f101..a67294ecadb 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -894,8 +894,8 @@ public class LibvirtVMDef { } } - public void defISODisk(String volPath) { - _diskType = DiskType.FILE; + public void defISODisk(String volPath, DiskType diskType) { + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; _diskLabel = getDevLabel(3, DiskBus.IDE, true); @@ -904,8 +904,8 @@ public class LibvirtVMDef { _bus = DiskBus.IDE; } - public void defISODisk(String volPath, boolean isUefiEnabled) { - _diskType = DiskType.FILE; + public void defISODisk(String volPath, boolean isUefiEnabled, DiskType diskType) { + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; _bus = isUefiEnabled ? DiskBus.SATA : DiskBus.IDE; @@ -914,18 +914,18 @@ public class LibvirtVMDef { _diskCacheMode = DiskCacheMode.NONE; } - public void defISODisk(String volPath, Integer devId) { - defISODisk(volPath, devId, null); + public void defISODisk(String volPath, Integer devId, DiskType diskType) { + defISODisk(volPath, devId, null, diskType); } - public void defISODisk(String volPath, Integer devId, String diskLabel) { + public void defISODisk(String volPath, Integer devId, String diskLabel, DiskType diskType) { if (devId == null && StringUtils.isBlank(diskLabel)) { LOGGER.debug(String.format("No ID or label informed for volume [%s].", volPath)); - defISODisk(volPath); + defISODisk(volPath, diskType); return; } - _diskType = DiskType.FILE; + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; @@ -942,11 +942,11 @@ public class LibvirtVMDef { _bus = DiskBus.IDE; } - public void defISODisk(String volPath, Integer devId,boolean isSecure) { + public void defISODisk(String volPath, Integer devId, boolean isSecure, DiskType diskType) { if (!isSecure) { - defISODisk(volPath, devId); + defISODisk(volPath, devId, diskType); } else { - _diskType = DiskType.FILE; + _diskType = diskType; _deviceType = DeviceType.CDROM; _sourcePath = volPath; _diskLabel = getDevLabel(devId, DiskBus.SATA, true); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java index cc955e86d8a..3ba1bc11975 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java @@ -130,34 +130,25 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper temporaryDisks = xmlParser == null ? - getTemporaryDisksWithPrefixFromTemporaryPool(temporaryStoragePool, temporaryConvertPath, temporaryConvertUuid) : - getTemporaryDisksFromParsedXml(temporaryStoragePool, xmlParser, convertedBasePath); - - List destinationDisks = moveTemporaryDisksToDestination(temporaryDisks, - destinationStoragePools, storagePoolMgr); - - cleanupDisksAndDomainFromTemporaryLocation(temporaryDisks, temporaryStoragePool, temporaryConvertUuid); - - UnmanagedInstanceTO convertedInstanceTO = getConvertedUnmanagedInstance(temporaryConvertUuid, - destinationDisks, xmlParser); - return new ConvertInstanceAnswer(cmd, convertedInstanceTO); + return new ConvertInstanceAnswer(cmd, temporaryConvertUuid); } catch (Exception e) { String error = String.format("Error converting instance %s from %s, due to: %s", sourceInstanceName, sourceHypervisorType, e.getMessage()); logger.error(error, e); + cleanupSecondaryStorage = true; return new ConvertInstanceAnswer(cmd, false, error); } finally { if (ovfExported && StringUtils.isNotBlank(ovfTemplateDirOnConversionLocation)) { @@ -165,7 +156,7 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(ImportConvertedInstanceCommand cmd, LibvirtComputingResource serverResource) { + RemoteInstanceTO sourceInstance = cmd.getSourceInstance(); + Hypervisor.HypervisorType sourceHypervisorType = sourceInstance.getHypervisorType(); + String sourceInstanceName = sourceInstance.getInstanceName(); + List destinationStoragePools = cmd.getDestinationStoragePools(); + DataStoreTO conversionTemporaryLocation = cmd.getConversionTemporaryLocation(); + final String temporaryConvertUuid = cmd.getTemporaryConvertUuid(); + + final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr(); + KVMStoragePool temporaryStoragePool = getTemporaryStoragePool(conversionTemporaryLocation, storagePoolMgr); + final String temporaryConvertPath = temporaryStoragePool.getLocalPath(); + + try { + String convertedBasePath = String.format("%s/%s", temporaryConvertPath, temporaryConvertUuid); + LibvirtDomainXMLParser xmlParser = parseMigratedVMXmlDomain(convertedBasePath); + + List temporaryDisks = xmlParser == null ? + getTemporaryDisksWithPrefixFromTemporaryPool(temporaryStoragePool, temporaryConvertPath, temporaryConvertUuid) : + getTemporaryDisksFromParsedXml(temporaryStoragePool, xmlParser, convertedBasePath); + + List destinationDisks = moveTemporaryDisksToDestination(temporaryDisks, + destinationStoragePools, storagePoolMgr); + + cleanupDisksAndDomainFromTemporaryLocation(temporaryDisks, temporaryStoragePool, temporaryConvertUuid); + + UnmanagedInstanceTO convertedInstanceTO = getConvertedUnmanagedInstance(temporaryConvertUuid, + destinationDisks, xmlParser); + return new ImportConvertedInstanceAnswer(cmd, convertedInstanceTO); + } catch (Exception e) { + String error = String.format("Error converting instance %s from %s, due to: %s", + sourceInstanceName, sourceHypervisorType, e.getMessage()); + logger.error(error, e); + return new ImportConvertedInstanceAnswer(cmd, false, error); + } finally { + if (conversionTemporaryLocation instanceof NfsTO) { + logger.debug("Cleaning up secondary storage temporary location"); + storagePoolMgr.deleteStoragePool(temporaryStoragePool.getType(), temporaryStoragePool.getUuid()); + } + } + } + + protected KVMStoragePool getTemporaryStoragePool(DataStoreTO conversionTemporaryLocation, KVMStoragePoolManager storagePoolMgr) { + if (conversionTemporaryLocation instanceof NfsTO) { + NfsTO nfsTO = (NfsTO) conversionTemporaryLocation; + return storagePoolMgr.getStoragePoolByURI(nfsTO.getUrl()); + } else { + PrimaryDataStoreTO primaryDataStoreTO = (PrimaryDataStoreTO) conversionTemporaryLocation; + return storagePoolMgr.getStoragePool(primaryDataStoreTO.getPoolType(), primaryDataStoreTO.getUuid()); + } + } + + protected List getTemporaryDisksFromParsedXml(KVMStoragePool pool, LibvirtDomainXMLParser xmlParser, String convertedBasePath) { + List disksDefs = xmlParser.getDisks(); + disksDefs = disksDefs.stream().filter(x -> x.getDiskType() == LibvirtVMDef.DiskDef.DiskType.FILE && + x.getDeviceType() == LibvirtVMDef.DiskDef.DeviceType.DISK).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(disksDefs)) { + String err = String.format("Cannot find any disk defined on the converted XML domain %s.xml", convertedBasePath); + logger.error(err); + throw new CloudRuntimeException(err); + } + sanitizeDisksPath(disksDefs); + return getPhysicalDisksFromDefPaths(disksDefs, pool); + } + + private List getPhysicalDisksFromDefPaths(List disksDefs, KVMStoragePool pool) { + List disks = new ArrayList<>(); + for (LibvirtVMDef.DiskDef diskDef : disksDefs) { + KVMPhysicalDisk physicalDisk = pool.getPhysicalDisk(diskDef.getDiskPath()); + disks.add(physicalDisk); + } + return disks; + } + + protected List getTemporaryDisksWithPrefixFromTemporaryPool(KVMStoragePool pool, String path, String prefix) { + String msg = String.format("Could not parse correctly the converted XML domain, checking for disks on %s with prefix %s", path, prefix); + logger.info(msg); + pool.refresh(); + List disksWithPrefix = pool.listPhysicalDisks() + .stream() + .filter(x -> x.getName().startsWith(prefix) && !x.getName().endsWith(".xml")) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(disksWithPrefix)) { + msg = String.format("Could not find any converted disk with prefix %s on temporary location %s", prefix, path); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + return disksWithPrefix; + } + + private void cleanupDisksAndDomainFromTemporaryLocation(List disks, + KVMStoragePool temporaryStoragePool, + String temporaryConvertUuid) { + for (KVMPhysicalDisk disk : disks) { + logger.info(String.format("Cleaning up temporary disk %s after conversion from temporary location", disk.getName())); + temporaryStoragePool.deletePhysicalDisk(disk.getName(), Storage.ImageFormat.QCOW2); + } + logger.info(String.format("Cleaning up temporary domain %s after conversion from temporary location", temporaryConvertUuid)); + FileUtil.deleteFiles(temporaryStoragePool.getLocalPath(), temporaryConvertUuid, ".xml"); + } + + protected void sanitizeDisksPath(List disks) { + for (LibvirtVMDef.DiskDef disk : disks) { + String[] diskPathParts = disk.getDiskPath().split("/"); + String relativePath = diskPathParts[diskPathParts.length - 1]; + disk.setDiskPath(relativePath); + } + } + + protected List moveTemporaryDisksToDestination(List temporaryDisks, + List destinationStoragePools, + KVMStoragePoolManager storagePoolMgr) { + List targetDisks = new ArrayList<>(); + if (temporaryDisks.size() != destinationStoragePools.size()) { + String warn = String.format("Discrepancy between the converted instance disks (%s) " + + "and the expected number of disks (%s)", temporaryDisks.size(), destinationStoragePools.size()); + logger.warn(warn); + } + for (int i = 0; i < temporaryDisks.size(); i++) { + String poolPath = destinationStoragePools.get(i); + KVMStoragePool destinationPool = storagePoolMgr.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, poolPath); + if (destinationPool == null) { + String err = String.format("Could not find a storage pool by URI: %s", poolPath); + logger.error(err); + continue; + } + if (destinationPool.getType() != Storage.StoragePoolType.NetworkFilesystem) { + String err = String.format("Storage pool by URI: %s is not an NFS storage", poolPath); + logger.error(err); + continue; + } + KVMPhysicalDisk sourceDisk = temporaryDisks.get(i); + if (logger.isDebugEnabled()) { + String msg = String.format("Trying to copy converted instance disk number %s from the temporary location %s" + + " to destination storage pool %s", i, sourceDisk.getPool().getLocalPath(), destinationPool.getUuid()); + logger.debug(msg); + } + + String destinationName = UUID.randomUUID().toString(); + + KVMPhysicalDisk destinationDisk = storagePoolMgr.copyPhysicalDisk(sourceDisk, destinationName, destinationPool, 7200 * 1000); + targetDisks.add(destinationDisk); + } + return targetDisks; + } + + private UnmanagedInstanceTO getConvertedUnmanagedInstance(String baseName, + List vmDisks, + LibvirtDomainXMLParser xmlParser) { + UnmanagedInstanceTO instanceTO = new UnmanagedInstanceTO(); + instanceTO.setName(baseName); + instanceTO.setDisks(getUnmanagedInstanceDisks(vmDisks, xmlParser)); + instanceTO.setNics(getUnmanagedInstanceNics(xmlParser)); + return instanceTO; + } + + private List getUnmanagedInstanceNics(LibvirtDomainXMLParser xmlParser) { + List nics = new ArrayList<>(); + if (xmlParser != null) { + List interfaces = xmlParser.getInterfaces(); + for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) { + UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic(); + nic.setMacAddress(interfaceDef.getMacAddress()); + nic.setNicId(interfaceDef.getBrName()); + nic.setAdapterType(interfaceDef.getModel().toString()); + nics.add(nic); + } + } + return nics; + } + + protected List getUnmanagedInstanceDisks(List vmDisks, LibvirtDomainXMLParser xmlParser) { + List instanceDisks = new ArrayList<>(); + List diskDefs = xmlParser != null ? xmlParser.getDisks() : null; + for (int i = 0; i< vmDisks.size(); i++) { + KVMPhysicalDisk physicalDisk = vmDisks.get(i); + KVMStoragePool storagePool = physicalDisk.getPool(); + UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk(); + disk.setPosition(i); + Pair storagePoolHostAndPath = getNfsStoragePoolHostAndPath(storagePool); + disk.setDatastoreHost(storagePoolHostAndPath.first()); + disk.setDatastorePath(storagePoolHostAndPath.second()); + disk.setDatastoreName(storagePool.getUuid()); + disk.setDatastoreType(storagePool.getType().name()); + disk.setCapacity(physicalDisk.getVirtualSize()); + disk.setFileBaseName(physicalDisk.getName()); + if (CollectionUtils.isNotEmpty(diskDefs)) { + LibvirtVMDef.DiskDef diskDef = diskDefs.get(i); + disk.setController(diskDef.getBusType() != null ? diskDef.getBusType().toString() : LibvirtVMDef.DiskDef.DiskBus.VIRTIO.toString()); + } else { + // If the job is finished but we cannot parse the XML, the guest VM can use the virtio driver + disk.setController(LibvirtVMDef.DiskDef.DiskBus.VIRTIO.toString()); + } + instanceDisks.add(disk); + } + return instanceDisks; + } + + protected Pair getNfsStoragePoolHostAndPath(KVMStoragePool storagePool) { + String sourceHostIp = null; + String sourcePath = null; + List commands = new ArrayList<>(); + commands.add(new String[]{Script.getExecutableAbsolutePath("mount")}); + commands.add(new String[]{Script.getExecutableAbsolutePath("grep"), storagePool.getLocalPath()}); + String storagePoolMountPoint = Script.executePipedCommands(commands, 0).second(); + logger.debug(String.format("NFS Storage pool: %s - local path: %s, mount point: %s", storagePool.getUuid(), storagePool.getLocalPath(), storagePoolMountPoint)); + if (StringUtils.isNotEmpty(storagePoolMountPoint)) { + String[] res = storagePoolMountPoint.strip().split(" "); + res = res[0].split(":"); + if (res.length > 1) { + sourceHostIp = res[0].strip(); + sourcePath = res[1].strip(); + } + } + return new Pair<>(sourceHostIp, sourcePath); + } + + protected LibvirtDomainXMLParser parseMigratedVMXmlDomain(String installPath) throws IOException { + String xmlPath = String.format("%s.xml", installPath); + if (!new File(xmlPath).exists()) { + String err = String.format("Conversion failed. Unable to find the converted XML domain, expected %s", xmlPath); + logger.error(err); + throw new CloudRuntimeException(err); + } + InputStream is = new BufferedInputStream(new FileInputStream(xmlPath)); + String xml = IOUtils.toString(is, Charset.defaultCharset()); + final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); + try { + parser.parseDomainXML(xml); + return parser; + } catch (RuntimeException e) { + String err = String.format("Error parsing the converted instance XML domain at %s: %s", xmlPath, e.getMessage()); + logger.error(err, e); + logger.debug(xml); + return null; + } + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index ffffe877e6e..fe6be10f9b8 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -1119,11 +1119,12 @@ public class KVMStorageProcessor implements StorageProcessor { storagePool = storagePoolMgr.getStoragePoolByURI(path); } final KVMPhysicalDisk isoVol = storagePool.getPhysicalDisk(name); + final DiskDef.DiskType isoDiskType = LibvirtComputingResource.getDiskType(isoVol); isoPath = isoVol.getPath(); - iso.defISODisk(isoPath, isUefiEnabled); + iso.defISODisk(isoPath, isUefiEnabled, isoDiskType); } else { - iso.defISODisk(null, isUefiEnabled); + iso.defISODisk(null, isUefiEnabled, DiskDef.DiskType.FILE); } final List disks = resource.getDisks(conn, vmName); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index c91c001858d..1ce901d857f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -173,7 +173,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { * Checks if downloaded template is extractable * @return true if it should be extracted, false if not */ - private boolean isTemplateExtractable(String templatePath) { + public static boolean isTemplateExtractable(String templatePath) { String type = Script.runSimpleBashScript("file " + templatePath + " | awk -F' ' '{print $2}'"); return type.equalsIgnoreCase("bzip2") || type.equalsIgnoreCase("gzip") || type.equalsIgnoreCase("zip"); } @@ -183,7 +183,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { * @param downloadedTemplateFile * @param templateUuid */ - private String getExtractCommandForDownloadedFile(String downloadedTemplateFile, String templateUuid) { + public static String getExtractCommandForDownloadedFile(String downloadedTemplateFile, String templateUuid) { if (downloadedTemplateFile.endsWith(".zip")) { return "unzip -p " + downloadedTemplateFile + " | cat > " + templateUuid; } else if (downloadedTemplateFile.endsWith(".bz2")) { @@ -198,7 +198,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { /** * Extract downloaded template into installPath, remove compressed file */ - private void extractDownloadedTemplate(String downloadedTemplateFile, KVMStoragePool destPool, String destinationFile) { + public static void extractDownloadedTemplate(String downloadedTemplateFile, KVMStoragePool destPool, String destinationFile) { String extractCommand = getExtractCommandForDownloadedFile(downloadedTemplateFile, destinationFile); Script.runSimpleBashScript(extractCommand); Script.runSimpleBashScript("rm -f " + downloadedTemplateFile); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java index f0e94e59485..9537469145f 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java @@ -18,7 +18,6 @@ // package com.cloud.hypervisor.kvm.resource.wrapper; -import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -80,7 +79,6 @@ public class LibvirtConvertInstanceCommandWrapperTest { Mockito.when(storagePoolManager.getStoragePoolByURI(secondaryPoolUrl)).thenReturn(temporaryPool); KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class); KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2)); } @Test @@ -107,51 +105,6 @@ public class LibvirtConvertInstanceCommandWrapperTest { Assert.assertNotNull(temporaryStoragePool); } - @Test - public void testGetTemporaryDisksWithPrefixFromTemporaryPool() { - String convertPath = "/xyz"; - String convertPrefix = UUID.randomUUID().toString(); - KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(physicalDisk1.getName()).thenReturn("disk1"); - KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(physicalDisk2.getName()).thenReturn("disk2"); - - KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(convertedDisk1.getName()).thenReturn(String.format("%s-sda", convertPrefix)); - KVMPhysicalDisk convertedDisk2 = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(convertedDisk2.getName()).thenReturn(String.format("%s-sdb", convertPrefix)); - KVMPhysicalDisk convertedXml = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(convertedXml.getName()).thenReturn(String.format("%s.xml", convertPrefix)); - Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2, - convertedDisk1, convertedDisk2, convertedXml)); - - List convertedDisks = convertInstanceCommandWrapper.getTemporaryDisksWithPrefixFromTemporaryPool(temporaryPool, convertPath, convertPrefix); - Assert.assertEquals(2, convertedDisks.size()); - } - - @Test - public void testGetTemporaryDisksFromParsedXml() { - String relativePath = UUID.randomUUID().toString(); - String fullPath = String.format("/mnt/xyz/%s", relativePath); - - LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef(); - LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO; - LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2; - diskDef.defFileBasedDisk(fullPath, "test", bus, type); - - LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class); - Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef)); - - KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class); - Mockito.when(convertedDisk1.getName()).thenReturn("disk1"); - Mockito.when(temporaryPool.getPhysicalDisk(relativePath)).thenReturn(convertedDisk1); - - List disks = convertInstanceCommandWrapper.getTemporaryDisksFromParsedXml(temporaryPool, parser, ""); - Mockito.verify(convertInstanceCommandWrapper).sanitizeDisksPath(List.of(diskDef)); - Assert.assertEquals(1, disks.size()); - Assert.assertEquals("disk1", disks.get(0).getName()); - } - @Test public void testSanitizeDisksPath() { String relativePath = UUID.randomUUID().toString(); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapperTest.java new file mode 100644 index 00000000000..343a15b367d --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapperTest.java @@ -0,0 +1,245 @@ +// +// 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.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ImportConvertedInstanceCommand; +import com.cloud.agent.api.to.NfsTO; +import com.cloud.agent.api.to.RemoteInstanceTO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtImportConvertedInstanceCommandWrapperTest { + + @Spy + private LibvirtImportConvertedInstanceCommandWrapper importInstanceCommandWrapper = + Mockito.spy(LibvirtImportConvertedInstanceCommandWrapper.class); + + @Mock + private LibvirtComputingResource libvirtComputingResourceMock; + + @Mock + private KVMStoragePool temporaryPool; + @Mock + private KVMStoragePool destinationPool; + @Mock + private PrimaryDataStoreTO primaryDataStore; + @Mock + private NfsTO secondaryDataStore; + @Mock + private KVMStoragePoolManager storagePoolManager; + + private static final String secondaryPoolUrl = "nfs://192.168.1.1/secondary"; + private static final String vmName = "VmToImport"; + + @Before + public void setUp() { + Mockito.when(secondaryDataStore.getUrl()).thenReturn(secondaryPoolUrl); + Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolManager); + Mockito.when(storagePoolManager.getStoragePoolByURI(secondaryPoolUrl)).thenReturn(temporaryPool); + KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class); + KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2)); + } + + @Test + public void testGetTemporaryStoragePool() { + KVMStoragePool temporaryStoragePool = importInstanceCommandWrapper.getTemporaryStoragePool(secondaryDataStore, libvirtComputingResourceMock.getStoragePoolMgr()); + Assert.assertNotNull(temporaryStoragePool); + } + + @Test + public void testGetTemporaryDisksWithPrefixFromTemporaryPool() { + String convertPath = "/xyz"; + String convertPrefix = UUID.randomUUID().toString(); + KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(physicalDisk1.getName()).thenReturn("disk1"); + KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(physicalDisk2.getName()).thenReturn("disk2"); + + KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(convertedDisk1.getName()).thenReturn(String.format("%s-sda", convertPrefix)); + KVMPhysicalDisk convertedDisk2 = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(convertedDisk2.getName()).thenReturn(String.format("%s-sdb", convertPrefix)); + KVMPhysicalDisk convertedXml = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(convertedXml.getName()).thenReturn(String.format("%s.xml", convertPrefix)); + Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2, + convertedDisk1, convertedDisk2, convertedXml)); + + List convertedDisks = importInstanceCommandWrapper.getTemporaryDisksWithPrefixFromTemporaryPool(temporaryPool, convertPath, convertPrefix); + Assert.assertEquals(2, convertedDisks.size()); + } + + @Test + public void testGetTemporaryDisksFromParsedXml() { + String relativePath = UUID.randomUUID().toString(); + String fullPath = String.format("/mnt/xyz/%s", relativePath); + + LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef(); + LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO; + LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2; + diskDef.defFileBasedDisk(fullPath, "test", bus, type); + + LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class); + Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef)); + + KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(convertedDisk1.getName()).thenReturn("disk1"); + Mockito.when(temporaryPool.getPhysicalDisk(relativePath)).thenReturn(convertedDisk1); + + List disks = importInstanceCommandWrapper.getTemporaryDisksFromParsedXml(temporaryPool, parser, ""); + Mockito.verify(importInstanceCommandWrapper).sanitizeDisksPath(List.of(diskDef)); + Assert.assertEquals(1, disks.size()); + Assert.assertEquals("disk1", disks.get(0).getName()); + } + + @Test + public void testSanitizeDisksPath() { + String relativePath = UUID.randomUUID().toString(); + String fullPath = String.format("/mnt/xyz/%s", relativePath); + LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef(); + LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO; + LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2; + diskDef.defFileBasedDisk(fullPath, "test", bus, type); + + importInstanceCommandWrapper.sanitizeDisksPath(List.of(diskDef)); + Assert.assertEquals(relativePath, diskDef.getDiskPath()); + } + + @Test + public void testMoveTemporaryDisksToDestination() { + KVMPhysicalDisk sourceDisk = Mockito.mock(KVMPhysicalDisk.class); + Mockito.lenient().when(sourceDisk.getPool()).thenReturn(temporaryPool); + List disks = List.of(sourceDisk); + String destinationPoolUuid = UUID.randomUUID().toString(); + List destinationPools = List.of(destinationPoolUuid); + + KVMPhysicalDisk destDisk = Mockito.mock(KVMPhysicalDisk.class); + Mockito.when(destDisk.getPath()).thenReturn("xyz"); + Mockito.when(storagePoolManager.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, destinationPoolUuid)) + .thenReturn(destinationPool); + Mockito.when(destinationPool.getType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); + Mockito.when(storagePoolManager.copyPhysicalDisk(Mockito.eq(sourceDisk), Mockito.anyString(), Mockito.eq(destinationPool), Mockito.anyInt())) + .thenReturn(destDisk); + + List movedDisks = importInstanceCommandWrapper.moveTemporaryDisksToDestination(disks, destinationPools, storagePoolManager); + Assert.assertEquals(1, movedDisks.size()); + Assert.assertEquals("xyz", movedDisks.get(0).getPath()); + } + + @Test + public void testGetUnmanagedInstanceDisks() { + try (MockedStatic