From 3526e62ed3f413a9d8e4af05edf3cc510b3217bd Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Wed, 30 Aug 2023 16:41:43 +0530 Subject: [PATCH 01/65] KVM Ingestion: Initial commit --- .../api/command/admin/vm/ImportVmCmd.java | 322 ++++++++++++++++++ .../command/admin/vm/ListVmsForImportCmd.java | 134 ++++++++ .../apache/cloudstack/vm/VmImportService.java | 7 + .../cloud/agent/api/GetRemoteVmsAnswer.java | 75 ++++ .../cloud/agent/api/GetRemoteVmsCommand.java | 71 ++++ .../resource/LibvirtComputingResource.java | 55 +++ .../LibvirtGetRemoteVmsCommandWrapper.java | 59 ++++ .../LibvirtComputingResourceTest.java | 10 + .../vm/UnmanagedVMsManagerImpl.java | 218 ++++++++++++ tools/apidoc/gen_toc.py | 4 +- .../java/com/cloud/utils/ssh/SshHelper.java | 21 +- 11 files changed, 971 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java create mode 100644 core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java create mode 100644 core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java 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 new file mode 100644 index 000000000000..44655040fde1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java @@ -0,0 +1,322 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.vm; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.offering.DiskOffering; +import com.cloud.user.Account; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.VmDetailConstants; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.vm.VmImportService; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@APICommand(name = "importVm", + description = "Import virtual machine from a unmanaged host", + responseObject = UserVmResponse.class, + responseView = ResponseObject.ResponseView.Full, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = true, + authorized = {RoleType.Admin}, + since = "4.19.0") +public class ImportVmCmd extends BaseAsyncCmd { + public static final Logger LOGGER = Logger.getLogger(ImportVmCmd.class); + + @Inject + public VmImportService vmImportService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + required = true, + description = "the hypervisor name of the instance") + private String name; + + @Parameter(name = ApiConstants.DISPLAY_NAME, + type = CommandType.STRING, + description = "the display name of the instance") + private String displayName; + + @Parameter(name = ApiConstants.HOST_NAME, + type = CommandType.STRING, + description = "the host name of the instance") + private String hostName; + + @Parameter(name = ApiConstants.ACCOUNT, + type = CommandType.STRING, + description = "an optional account for the virtual machine. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "import instance to the domain specified") + private Long domainId; + + @Parameter(name = ApiConstants.PROJECT_ID, + type = CommandType.UUID, + entityType = ProjectResponse.class, + description = "import instance for the project") + private Long projectId; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + required = true, + description = "the zone ID") + private Long zoneId; + + @Parameter(name = ApiConstants.USERNAME, + type = CommandType.STRING, + description = "the username for the host") + private String username; + + @Parameter(name = ApiConstants.PASSWORD, + type = CommandType.STRING, + description = "the password for the host") + private String password; + + @Parameter(name = ApiConstants.URL, + type = CommandType.STRING, + required = true, + description = "the host URL") + private String url; + + @Parameter(name = ApiConstants.HYPERVISOR, + type = CommandType.STRING, + required = true, + description = "hypervisor type of the host") + private String hypervisor; + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, + type = CommandType.UUID, + entityType = ServiceOfferingResponse.class, + required = true, + description = "the ID of the service offering for the virtual machine") + private Long serviceOfferingId; + + @Parameter(name = ApiConstants.NIC_NETWORK_LIST, + type = CommandType.MAP, + description = "VM nic to network id mapping using keys nic and network") + private Map nicNetworkList; + + @Parameter(name = ApiConstants.NIC_IP_ADDRESS_LIST, + type = CommandType.MAP, + description = "VM nic to ip address mapping using keys nic, ip4Address") + private Map nicIpAddressList; + + @Parameter(name = ApiConstants.DATADISK_OFFERING_LIST, + type = CommandType.MAP, + description = "datadisk template to disk-offering mapping using keys disk and diskOffering") + private Map dataDiskToDiskOfferingList; + + @Parameter(name = ApiConstants.DETAILS, + type = CommandType.MAP, + description = "used to specify the custom parameters.") + private Map details; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public String getName() { + return name; + } + + public String getDisplayName() { + return displayName; + } + + public String getHostName() { + return hostName; + } + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + public Long getProjectId() { + return projectId; + } + + + public Long getZoneId() { + return zoneId; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getUrl() { + return url; + } + + public String getHypervisor() { + return hypervisor; + } + + public Long getServiceOfferingId() { + return serviceOfferingId; + } + + public Map getNicNetworkList() { + Map nicNetworkMap = new HashMap<>(); + if (MapUtils.isNotEmpty(nicNetworkList)) { + for (Map entry : (Collection>)nicNetworkList.values()) { + String nic = entry.get(VmDetailConstants.NIC); + String networkUuid = entry.get(VmDetailConstants.NETWORK); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(String.format("nic, '%s', goes on net, '%s'", nic, networkUuid)); + } + if (StringUtils.isAnyEmpty(nic, networkUuid) || _entityMgr.findByUuid(Network.class, networkUuid) == null) { + throw new InvalidParameterValueException(String.format("Network ID: %s for NIC ID: %s is invalid", networkUuid, nic)); + } + nicNetworkMap.put(nic, _entityMgr.findByUuid(Network.class, networkUuid).getId()); + } + } + return nicNetworkMap; + } + + public Map getNicIpAddressList() { + Map nicIpAddressMap = new HashMap<>(); + if (MapUtils.isNotEmpty(nicIpAddressList)) { + for (Map entry : (Collection>)nicIpAddressList.values()) { + String nic = entry.get(VmDetailConstants.NIC); + String ipAddress = StringUtils.defaultIfEmpty(entry.get(VmDetailConstants.IP4_ADDRESS), null); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(String.format("nic, '%s', gets ip, '%s'", nic, ipAddress)); + } + if (StringUtils.isEmpty(nic)) { + throw new InvalidParameterValueException(String.format("NIC ID: '%s' is invalid for IP address mapping", nic)); + } + if (StringUtils.isEmpty(ipAddress)) { + throw new InvalidParameterValueException(String.format("Empty address for NIC ID: %s is invalid", nic)); + } + if (StringUtils.isNotEmpty(ipAddress) && !ipAddress.equals("auto") && !NetUtils.isValidIp4(ipAddress)) { + throw new InvalidParameterValueException(String.format("IP address '%s' for NIC ID: %s is invalid", ipAddress, nic)); + } + Network.IpAddresses ipAddresses = new Network.IpAddresses(ipAddress, null); + nicIpAddressMap.put(nic, ipAddresses); + } + } + return nicIpAddressMap; + } + + public Map getDataDiskToDiskOfferingList() { + Map dataDiskToDiskOfferingMap = new HashMap<>(); + if (MapUtils.isNotEmpty(dataDiskToDiskOfferingList)) { + for (Map entry : (Collection>)dataDiskToDiskOfferingList.values()) { + String disk = entry.get(VmDetailConstants.DISK); + String offeringUuid = entry.get(VmDetailConstants.DISK_OFFERING); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(String.format("disk, '%s', gets offering, '%s'", disk, offeringUuid)); + } + if (StringUtils.isAnyEmpty(disk, offeringUuid) || _entityMgr.findByUuid(DiskOffering.class, offeringUuid) == null) { + throw new InvalidParameterValueException(String.format("Disk offering ID: %s for disk ID: %s is invalid", offeringUuid, disk)); + } + dataDiskToDiskOfferingMap.put(disk, _entityMgr.findByUuid(DiskOffering.class, offeringUuid).getId()); + } + } + return dataDiskToDiskOfferingMap; + } + + public Map getDetails() { + if (MapUtils.isEmpty(details)) { + return new HashMap(); + } + + Collection paramsCollection = details.values(); + Map params = (Map) (paramsCollection.toArray())[0]; + return params; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_IMPORT; + } + + @Override + public String getEventDescription() { + return "Importing unmanaged VM"; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + UserVmResponse response = vmImportService.importVm(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + if (accountId == null) { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + accountId = account.getId(); + } else { + accountId = Account.ACCOUNT_ID_SYSTEM; + } + } + return accountId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java new file mode 100644 index 000000000000..d645664140d5 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java @@ -0,0 +1,134 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.vm; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UnmanagedInstanceResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; +import org.apache.cloudstack.vm.VmImportService; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +@APICommand(name = "listVmsForImport", + description = "Lists virtual machines on a unmanaged host", + responseObject = UnmanagedInstanceResponse.class, + responseView = ResponseObject.ResponseView.Full, + entityType = {UnmanagedInstanceTO.class}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = true, + authorized = {RoleType.Admin}, + since = "4.19.0") +public class ListVmsForImportCmd extends BaseListCmd { + public static final Logger LOGGER = Logger.getLogger(ListVmsForImportCmd.class.getName()); + + @Inject + public VmImportService vmImportService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + required = true, + description = "the zone ID") + private Long zoneId; + + @Parameter(name = ApiConstants.USERNAME, + type = CommandType.STRING, + description = "the username for the host") + private String username; + + @Parameter(name = ApiConstants.PASSWORD, + type = CommandType.STRING, + description = "the password for the host") + private String password; + + @Parameter(name = ApiConstants.URL, + type = CommandType.STRING, + required = true, + description = "the host URL") + private String url; + + @Parameter(name = ApiConstants.HYPERVISOR, + type = CommandType.STRING, + required = true, + description = "hypervisor type of the host") + private String hypervisor; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getUrl() { + return url; + } + + public String getHypervisor() { + return hypervisor; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + ListResponse response = vmImportService.listVmsForImport(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + return account.getId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java b/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java index cce284745413..d217caae6475 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java +++ b/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java @@ -18,7 +18,9 @@ package org.apache.cloudstack.vm; import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd; +import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd; import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd; +import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UnmanagedInstanceResponse; import org.apache.cloudstack.api.response.UserVmResponse; @@ -26,4 +28,9 @@ public interface VmImportService { ListResponse listUnmanagedInstances(ListUnmanagedInstancesCmd cmd); UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd); + + UserVmResponse importVm(ImportVmCmd cmd); + + ListResponse listVmsForImport(ListVmsForImportCmd cmd); + } diff --git a/core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java new file mode 100644 index 000000000000..8cd072f1da1d --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java @@ -0,0 +1,75 @@ +// 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; + +import java.util.HashMap; +import java.util.List; + +@LogLevel(LogLevel.Log4jLevel.Trace) +public class GetRemoteVmsAnswer extends Answer { + + private String remoteIp; + private HashMap unmanagedInstances; + + List vmNames; + + GetRemoteVmsAnswer() { + } + + public GetRemoteVmsAnswer(GetRemoteVmsCommand cmd, String details, HashMap unmanagedInstances) { + super(cmd, true, details); + this.remoteIp = cmd.getRemoteIp(); + this.unmanagedInstances = unmanagedInstances; + } + + public GetRemoteVmsAnswer(GetRemoteVmsCommand cmd, String details, List vmNames) { + super(cmd, true, details); + this.remoteIp = cmd.getRemoteIp(); + this.vmNames = vmNames; + } + + public String getRemoteIp() { + return remoteIp; + } + + public void setRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + } + + public HashMap getUnmanagedInstances() { + return unmanagedInstances; + } + + public void setUnmanagedInstances(HashMap unmanagedInstances) { + this.unmanagedInstances = unmanagedInstances; + } + + public List getVmNames() { + return vmNames; + } + + public void setVmNames(List vmNames) { + this.vmNames = vmNames; + } + + public String getString() { + return "GetRemoteVmsAnswer [remoteIp=" + remoteIp + "]"; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.java b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.java new file mode 100644 index 000000000000..49abc160f964 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.java @@ -0,0 +1,71 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +@LogLevel(LogLevel.Log4jLevel.Trace) +public class GetRemoteVmsCommand extends Command { + + String remoteIp; + String username; + String password; + + public GetRemoteVmsCommand(String remoteIp, String username, String password) { + this.remoteIp = remoteIp; + this.username = username; + this.password = password; + } + + public String getRemoteIp() { + return remoteIp; + } + + public void setRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public GetRemoteVmsCommand() { + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getString() { + return "GetRemoteVmsCommand [remoteIp=" + remoteIp + "]"; + } +} + 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 8242d244efaf..d88db8477d28 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 @@ -5258,4 +5258,59 @@ public void setInterfaceDefQueueSettings(Map details, Integer cp } } } + + public List getStoppedVms(final Connect conn) { + final List stoppedVms = new ArrayList<>(); + + String[] vms = null; + int[] ids = null; + + try { + ids = conn.listDomains(); + } catch (final LibvirtException e) { + s_logger.warn("Unable to listDomains", e); + return null; + } + + Domain dm = null; + for (int i = 0; i < ids.length; i++) { + try { + dm = conn.domainLookupByID(ids[i]); + + final DomainState ps = dm.getInfo().state; + + final PowerState state = convertToPowerState(ps); + + s_logger.trace("VM " + dm.getName() + ": powerstate = " + ps + "; vm state=" + state.toString()); + final String vmName = dm.getName(); + + if (state == PowerState.PowerOff) { + stoppedVms.add(vmName); + } + } catch (final LibvirtException e) { + s_logger.warn("Unable to get vms", e); + } finally { + try { + if (dm != null) { + dm.free(); + } + } catch (final LibvirtException e) { + s_logger.trace("Ignoring libvirt error.", e); + } + } + } + + return stoppedVms; + } + + /* + Scp volume from remote host to local directory + */ + public void copyVolume(String srcIp, String username, String password, String localDir, String remoteFile) { + try { + SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, remoteFile); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java new file mode 100644 index 000000000000..a3b4c002c37f --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -0,0 +1,59 @@ +// +// 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.GetRemoteVmsAnswer; +import com.cloud.agent.api.GetRemoteVmsCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.resource.LibvirtConnection; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import org.apache.log4j.Logger; +import org.libvirt.Connect; +import org.libvirt.LibvirtException; + +import java.util.List; + +@ResourceWrapper(handles = GetRemoteVmsCommand.class) +public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtGetRemoteVmsCommandWrapper.class); + + @Override + public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingResource libvirtComputingResource) { + String result = null; + StringBuilder sb = new StringBuilder("qemu+ssh://"); + sb.append(command.getUsername()); + sb.append("@"); + sb.append(command.getRemoteIp()); + sb.append("/system"); + String hypervisorURI = sb.toString(); + try { + Connect conn = LibvirtConnection.getConnection(hypervisorURI); + List vmNames = libvirtComputingResource.getStoppedVms(conn); + return new GetRemoteVmsAnswer(command, "", vmNames); + } catch (final LibvirtException e) { + s_logger.error("Error while listing stopped Vms on remote host: "+ e.getMessage()); + return new Answer(command, false, result); + } + } + +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index fc462adc86dc..aa31ea9b9d3e 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -6200,4 +6200,14 @@ public void setupMemoryBalloonStatsPeriodTestSkipVm() throws LibvirtException { Mockito.verify(loggerMock).debug("Skipping the memory balloon stats period setting for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name] because this" + " VM has no memory balloon."); } + + @Test + public void migrateVmDisk() { + String secMnt = "/mnt/sec"; + String srcHost = "10.0.35.222"; + String remoteFile = "/mnt/76de4042-dc63-3837-9eec-c68ee28dca8f/e8d8c3f5-e6c4-4d8a-8248-f2a59563743c"; + String username = "root"; + String password = "P@ssword123"; + libvirtComputingResourceSpy.copyVolume(srcHost, username, password, secMnt, remoteFile); + } } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 752ad5a9fba9..326458e0ae58 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -26,13 +26,18 @@ import javax.inject.Inject; +import com.cloud.agent.api.GetRemoteVmsAnswer; +import com.cloud.agent.api.GetRemoteVmsCommand; +import com.cloud.dc.DataCenterVO; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd; +import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd; import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd; +import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd; import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.NicResponse; @@ -1238,6 +1243,8 @@ public List> getCommands() { cmdList.add(ListUnmanagedInstancesCmd.class); cmdList.add(ImportUnmanagedInstanceCmd.class); cmdList.add(UnmanageVMInstanceCmd.class); + cmdList.add(ListVmsForImportCmd.class); + cmdList.add(ImportVmCmd.class); return cmdList; } @@ -1347,6 +1354,217 @@ private boolean existsVMToUnmanage(String instanceName, Long hostId) { return answer.getResult(); } + @Override + public UserVmResponse importVm(ImportVmCmd cmd) { + final Account caller = CallContext.current().getCallingAccount(); + if (caller.getType() != Account.Type.ADMIN) { + throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid())); + } + final Long zoneId = cmd.getZoneId(); + final DataCenterVO zone = dataCenterDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("Please specify a valid zone."); + } + final String hypervisorType = cmd.getHypervisor(); + if (Hypervisor.HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) { + if (StringUtils.isBlank(cmd.getUsername())) { + throw new InvalidParameterValueException("Username need to be provided."); + } + } else { + throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor: %s", hypervisorType)); + } + final String instanceName = cmd.getName(); + if (StringUtils.isEmpty(instanceName)) { + throw new InvalidParameterValueException(String.format("Instance name cannot be empty")); + } + if (cmd.getDomainId() != null && StringUtils.isEmpty(cmd.getAccountName())) { + throw new InvalidParameterValueException("domainid parameter must be specified with account parameter"); + } + final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId()); + long userId = CallContext.current().getCallingUserId(); + List userVOs = userDao.listByAccount(owner.getAccountId()); + if (CollectionUtils.isNotEmpty(userVOs)) { + userId = userVOs.get(0).getId(); + } + VMTemplateVO template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME); + if (template == null) { + template = createDefaultDummyVmImportTemplate(); + if (template == null) { + throw new InvalidParameterValueException("Error while creating default Import Vm Template"); + } + } + + final Long serviceOfferingId = cmd.getServiceOfferingId(); + if (serviceOfferingId == null) { + throw new InvalidParameterValueException(String.format("Service offering ID cannot be null")); + } + final ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId); + if (serviceOffering == null) { + throw new InvalidParameterValueException(String.format("Service offering ID: %d cannot be found", serviceOfferingId)); + } + accountService.checkAccess(owner, serviceOffering, zone); + try { + resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1); + } catch (ResourceAllocationException e) { + LOGGER.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage()))); + } + String displayName = cmd.getDisplayName(); + if (StringUtils.isEmpty(displayName)) { + displayName = instanceName; + } + String hostName = cmd.getHostName(); + if (StringUtils.isEmpty(hostName)) { + if (!NetUtils.verifyDomainNameLabel(instanceName, true)) { + throw new InvalidParameterValueException(String.format("Please provide hostname for the VM. VM name contains unsupported characters for it to be used as hostname")); + } + hostName = instanceName; + } + if (!NetUtils.verifyDomainNameLabel(hostName, true)) { + throw new InvalidParameterValueException("Invalid VM hostname. VM hostname can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit"); + } + + final Map nicNetworkMap = cmd.getNicNetworkList(); + final Map nicIpAddressMap = cmd.getNicIpAddressList(); + final Map dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList(); + final Map details = cmd.getDetails(); + + UserVm userVm = importKvmVirtualMachineInternal(instanceName, zone, + template, displayName, hostName, caller, owner, userId, + serviceOffering, dataDiskOfferingMap, + nicNetworkMap, nicIpAddressMap, + details);; + + if (userVm == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import Vm with name: %s ", instanceName)); + } + return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0); + + } + + private UserVm importKvmVirtualMachineInternal(final String instanceName, final DataCenter zone, + final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, + final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, + final Map nicNetworkMap, final Map callerNicIpAddressMap, + final Map details) { + UserVm userVm = null; + + Map allDetails = new HashMap<>(details); + if (serviceOffering.isDynamic()) { + allDetails.put(VmDetailConstants.CPU_NUMBER, String.valueOf(serviceOffering.getCpu())); + allDetails.put(VmDetailConstants.MEMORY, String.valueOf(serviceOffering.getRamSize())); + allDetails.put(VmDetailConstants.CPU_SPEED, String.valueOf(serviceOffering.getSpeed())); + } +/* + + try { + userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner, + null, caller, true, null, owner.getAccountId(), userId, + validatedServiceOffering, null, hostName, + cluster.getHypervisorType(), allDetails, powerState); + } catch (InsufficientCapacityException ice) { + LOGGER.error(String.format("Failed to import vm name: %s", instanceName), ice); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ice.getMessage()); + } + if (userVm == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName)); + } + List> diskProfileStoragePoolList = new ArrayList<>(); + try { + if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { + throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); + } + Long minIops = null; + if (details.containsKey("minIops")) { + minIops = Long.parseLong(details.get("minIops")); + } + Long maxIops = null; + if (details.containsKey("maxIops")) { + maxIops = Long.parseLong(details.get("maxIops")); + } + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), + (rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB), minIops, maxIops, + template, owner, null)); + long deviceId = 1L; + for (UnmanagedInstanceTO.Disk disk : dataDisks) { + if (disk.getCapacity() == null || disk.getCapacity() == 0) { + throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId())); + } + DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); + diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), + (disk.getCapacity() / Resource.ResourceType.bytesToGiB), offering.getMinIops(), offering.getMaxIops(), + template, owner, deviceId)); + deviceId++; + } + } catch (Exception e) { + LOGGER.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); + } + try { + int nicIndex = 0; + for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { + Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); + Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, forced); + nicIndex++; + } + } catch (Exception e) { + LOGGER.error(String.format("Failed to import NICs while importing vm: %s", instanceName), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); + } + if (migrateAllowed) { + userVm = migrateImportedVM(host, template, validatedServiceOffering, userVm, owner, diskProfileStoragePoolList); + } + publishVMUsageUpdateResourceCount(userVm, validatedServiceOffering); */ + return userVm; + } + + public ListResponse listVmsForImport(ListVmsForImportCmd cmd) { + final Account caller = CallContext.current().getCallingAccount(); + if (caller.getType() != Account.Type.ADMIN) { + throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid())); + } + final Long zoneId = cmd.getZoneId(); + final DataCenterVO zone = dataCenterDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("Please specify a valid zone."); + } + final String hypervisorType = cmd.getHypervisor(); + if (Hypervisor.HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) { + if (StringUtils.isBlank(cmd.getUsername())) { + throw new InvalidParameterValueException("Username need to be provided."); + } + } else { + throw new InvalidParameterValueException(String.format("VM Import is currently not supported for hypervisor: %s", hypervisorType)); + } + + String keyword = cmd.getKeyword(); + if (StringUtils.isNotEmpty(keyword)) { + keyword = keyword.toLowerCase(); + } + + List hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.KVM, zoneId); + if(hosts.size() < 1) { + throw new CloudRuntimeException("No hosts available for Vm Import"); + } + HostVO host = hosts.get(0); + GetRemoteVmsCommand getRemoteVmsCommand = new GetRemoteVmsCommand(cmd.getUrl(), cmd.getUsername(), cmd.getPassword()); + Answer answer = agentManager.easySend(host.getId(), getRemoteVmsCommand); + if (!(answer instanceof GetRemoteVmsAnswer)) { + throw new CloudRuntimeException("Error while listing remote Vms"); + } + GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer; + List responses = new ArrayList<>(); + ListResponse listResponses = new ListResponse<>(); + listResponses.setResponses(responses, responses.size()); + return listResponses; + } + + @Override public String getConfigComponentName() { return UnmanagedVMsManagerImpl.class.getSimpleName(); diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index c07d27ae06cd..0fc7bc9e8e51 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -259,7 +259,9 @@ 'listQuarantinedIp': 'IP Quarantine', 'updateQuarantinedIp': 'IP Quarantine', 'removeQuarantinedIp': 'IP Quarantine', - 'Shutdown': 'Shutdown' + 'Shutdown': 'Shutdown', + 'listVmsForImport': 'Virtual Machine', + 'importVm': 'Virtual Machine' } diff --git a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java index 7a73e8675a70..6a2aa8278560 100644 --- a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java +++ b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java @@ -65,6 +65,10 @@ public static void scpTo(String host, int port, String user, File pemKeyFile, St } public static void scpFrom(String host, int port, String user, File permKeyFile, String localTargetDirectory, String remoteTargetFile) throws Exception { + scpFrom(host, port, user, permKeyFile, null, localTargetDirectory, remoteTargetFile); + } + + public static void scpFrom(String host, int port, String user, File permKeyFile, String password, String localTargetDirectory, String remoteTargetFile) throws Exception { com.trilead.ssh2.Connection conn = null; com.trilead.ssh2.SCPClient scpClient = null; @@ -72,11 +76,20 @@ public static void scpFrom(String host, int port, String user, File permKeyFile, conn = new com.trilead.ssh2.Connection(host, port); conn.connect(null, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT); - if (!conn.authenticateWithPublicKey(user, permKeyFile, null)) { - String msg = "Failed to authentication SSH user " + user + " on host " + host; - s_logger.error(msg); - throw new Exception(msg); + if (permKeyFile == null) { + if (!conn.authenticateWithPassword(user, password)) { + String msg = "Failed to authentication SSH user " + user + " on host " + host; + s_logger.error(msg); + throw new Exception(msg); + } + } else { + if (!conn.authenticateWithPublicKey(user, permKeyFile, password)) { + String msg = "Failed to authentication SSH user " + user + " on host " + host; + s_logger.error(msg); + throw new Exception(msg); + } } + scpClient = conn.createSCPClient(); scpClient.get(remoteTargetFile, localTargetDirectory); From 258c59810f89de82a668892cc73b60267be4f89d Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Thu, 31 Aug 2023 14:14:23 +0530 Subject: [PATCH 02/65] Add CopyRemoteVolume command --- .../agent/api/CopyRemoteVolumeAnswer.java | 44 +++++++ .../agent/api/CopyRemoteVolumeCommand.java | 89 ++++++++++++++ .../resource/LibvirtComputingResource.java | 17 +-- ...LibvirtCopyRemoteVolumeCommandWrapper.java | 53 +++++++++ .../LibvirtGetRemoteVmsCommandWrapper.java | 1 + .../vm/UnmanagedVMsManagerImpl.java | 110 +++++++++++++++--- 6 files changed, 287 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java create mode 100644 core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java diff --git a/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java new file mode 100644 index 000000000000..76f257a9d71e --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.agent.api; + +@LogLevel(LogLevel.Log4jLevel.Trace) +public class CopyRemoteVolumeAnswer extends Answer { + + private String remoteIp; + + CopyRemoteVolumeAnswer() { + } + + public CopyRemoteVolumeAnswer(CopyRemoteVolumeCommand cmd, String details) { + super(cmd, true, details); + this.remoteIp = cmd.getRemoteIp(); + } + + public String getRemoteIp() { + return remoteIp; + } + + public void setRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + } + + public String getString() { + return "CopyRemoteVolumeAnswer [remoteIp=" + remoteIp + "]"; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java new file mode 100644 index 000000000000..617f2e8ea2f6 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java @@ -0,0 +1,89 @@ +// +// 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; + +@LogLevel(LogLevel.Log4jLevel.Trace) +public class CopyRemoteVolumeCommand extends Command { + + String remoteIp; + String username; + String password; + String srcFile; + String dstPath; + + public CopyRemoteVolumeCommand(String remoteIp, String username, String password) { + this.remoteIp = remoteIp; + this.username = username; + this.password = password; + } + + public String getRemoteIp() { + return remoteIp; + } + + public void setRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getSrcFile() { + return srcFile; + } + + public void setSrcFile(String srcFile) { + this.srcFile = srcFile; + } + + public String getDstPath() { + return dstPath; + } + + public void setDstPath(String dstPath) { + this.dstPath = dstPath; + } + + public CopyRemoteVolumeCommand() { + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getString() { + return "CopyRemoteVolumeCommand [remoteIp=" + remoteIp + "]"; + } +} + 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 d88db8477d28..5a568882f6a2 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 @@ -68,6 +68,7 @@ import org.apache.cloudstack.utils.qemu.QemuObject; import org.apache.cloudstack.utils.security.KeyStoreUtils; import org.apache.cloudstack.utils.security.ParserUtils; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.ArrayUtils; @@ -5262,31 +5263,31 @@ public void setInterfaceDefQueueSettings(Map details, Integer cp public List getStoppedVms(final Connect conn) { final List stoppedVms = new ArrayList<>(); - String[] vms = null; - int[] ids = null; + String[] vms; try { - ids = conn.listDomains(); + vms = conn.listDefinedDomains(); } catch (final LibvirtException e) { s_logger.warn("Unable to listDomains", e); return null; } - + Domain dm = null; - for (int i = 0; i < ids.length; i++) { + for (String vm : vms) { try { - dm = conn.domainLookupByID(ids[i]); + dm = conn.domainLookupByName(vm); + UnmanagedInstanceTO unmanagedInstanceTO = new UnmanagedInstanceTO(); final DomainState ps = dm.getInfo().state; final PowerState state = convertToPowerState(ps); - s_logger.trace("VM " + dm.getName() + ": powerstate = " + ps + "; vm state=" + state.toString()); + s_logger.debug("VM " + dm.getName() + ": powerstate = " + ps + "; vm state=" + state.toString()); final String vmName = dm.getName(); if (state == PowerState.PowerOff) { stoppedVms.add(vmName); - } + } } catch (final LibvirtException e) { s_logger.warn("Unable to get vms", e); } finally { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java new file mode 100644 index 000000000000..a35832cddea4 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java @@ -0,0 +1,53 @@ +// +// 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.CopyRemoteVolumeAnswer; +import com.cloud.agent.api.CopyRemoteVolumeCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import org.apache.log4j.Logger; + +@ResourceWrapper(handles = CopyRemoteVolumeCommand.class) +public final class LibvirtCopyRemoteVolumeCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtCopyRemoteVolumeCommandWrapper.class); + + @Override + public Answer execute(final CopyRemoteVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { + String result = null; + String srcIp = command.getRemoteIp(); + String username = command.getUsername(); + String password = command.getPassword(); + String srcFile = command.getSrcFile(); + String dstPath = command.getDstPath(); + try { + libvirtComputingResource.copyVolume(srcIp, username, password, dstPath, srcFile); + s_logger.debug("Volume Copy Successful "); + return new CopyRemoteVolumeAnswer(command, ""); + } catch (final Exception e) { + s_logger.error("Error while copying file from remote host: "+ e.getMessage()); + return new Answer(command, false, result); + } + } + +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index a3b4c002c37f..2fa0472efc4b 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -49,6 +49,7 @@ public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingR try { Connect conn = LibvirtConnection.getConnection(hypervisorURI); List vmNames = libvirtComputingResource.getStoppedVms(conn); + s_logger.debug("Vm Names: "+ vmNames.toString()); return new GetRemoteVmsAnswer(command, "", vmNames); } catch (final LibvirtException e) { s_logger.error("Error while listing stopped Vms on remote host: "+ e.getMessage()); diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 326458e0ae58..cfd01213a1ad 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -26,6 +26,8 @@ import javax.inject.Inject; +import com.cloud.agent.api.CopyRemoteVolumeAnswer; +import com.cloud.agent.api.CopyRemoteVolumeCommand; import com.cloud.agent.api.GetRemoteVmsAnswer; import com.cloud.agent.api.GetRemoteVmsCommand; import com.cloud.dc.DataCenterVO; @@ -40,8 +42,8 @@ import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd; import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd; import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.NicResponse; -import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse; +//import org.apache.cloudstack.api.response.NicResponse; +//import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse; import org.apache.cloudstack.api.response.UnmanagedInstanceResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; @@ -240,6 +242,7 @@ private VMTemplateVO createDefaultDummyVmImportTemplate() { private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) { UnmanagedInstanceResponse response = new UnmanagedInstanceResponse(); response.setName(instance.getName()); + /* if (cluster != null) { response.setClusterId(cluster.getUuid()); } @@ -294,6 +297,8 @@ private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInsta response.addNic(nicResponse); } } + + */ return response; } @@ -677,6 +682,40 @@ private Map getUnmanagedNicNetworkMap(String instanceName, List importKvmDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering, + Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template, + Account owner, Long deviceId, String remoteUrl) { + final DataCenter zone = dataCenterDao.findById(vm.getDataCenterId()); + final String path = StringUtils.isEmpty(disk.getFileBaseName()) ? disk.getImagePath() : disk.getFileBaseName(); + String chainInfo = disk.getChainInfo(); + if (StringUtils.isEmpty(chainInfo)) { + VirtualMachineDiskInfo diskInfo = new VirtualMachineDiskInfo(); + diskInfo.setDiskDeviceBusName(String.format("%s%d:%d", disk.getController(), disk.getControllerUnit(), disk.getPosition())); + diskInfo.setDiskChain(new String[]{disk.getImagePath()}); + chainInfo = gson.toJson(diskInfo); + } + StoragePool storagePool = getStoragePool(disk, zone, cluster); + CopyRemoteVolumeCommand copyRemoteVolumeCommand = new CopyRemoteVolumeCommand(); + copyRemoteVolumeCommand.setRemoteIp(remoteUrl); + copyRemoteVolumeCommand.setUsername("root"); + copyRemoteVolumeCommand.setPassword("P@ssword123"); + copyRemoteVolumeCommand.setSrcFile(disk.getFileBaseName()); + copyRemoteVolumeCommand.setDstPath(storagePool.getPath()); + Long hostId = vm.getHostId(); + Answer answer = agentManager.easySend(hostId, copyRemoteVolumeCommand); + if (!(answer instanceof CopyRemoteVolumeAnswer)) { + throw new CloudRuntimeException("Error while copying volume"); + } + CopyRemoteVolumeAnswer copyRemoteVolumeAnswer = (CopyRemoteVolumeAnswer) answer; + if(!copyRemoteVolumeAnswer.getResult()) { + throw new CloudRuntimeException("Error while copying volume"); + } + DiskProfile profile = volumeManager.importVolume(type, name, diskOffering, diskSize, + minIops, maxIops, vm, template, owner, deviceId, storagePool.getId(), path, chainInfo); + + return new Pair<>(profile, storagePool); + } + private Pair importDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering, Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template, Account owner, Long deviceId) { @@ -1429,11 +1468,13 @@ public UserVmResponse importVm(ImportVmCmd cmd) { final Map nicIpAddressMap = cmd.getNicIpAddressList(); final Map dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList(); final Map details = cmd.getDetails(); + UnmanagedInstanceTO unmanagedInstanceTO = new UnmanagedInstanceTO(); + String remoteUrl = cmd.getUrl(); - UserVm userVm = importKvmVirtualMachineInternal(instanceName, zone, + UserVm userVm = importKvmVirtualMachineInternal(unmanagedInstanceTO, instanceName, zone, template, displayName, hostName, caller, owner, userId, serviceOffering, dataDiskOfferingMap, - nicNetworkMap, nicIpAddressMap, + nicNetworkMap, nicIpAddressMap, remoteUrl, details);; if (userVm == null) { @@ -1443,26 +1484,50 @@ public UserVmResponse importVm(ImportVmCmd cmd) { } - private UserVm importKvmVirtualMachineInternal(final String instanceName, final DataCenter zone, + private UserVm importKvmVirtualMachineInternal(final UnmanagedInstanceTO unmanagedInstance, final String instanceName, final DataCenter zone, final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, final Map nicNetworkMap, final Map callerNicIpAddressMap, - final Map details) { + final String remoteUrl, final Map details) { UserVm userVm = null; + long hostId = 6; + HostVO host = hostDao.findById(hostId); + Cluster cluster = clusterDao.findById(host.getClusterId()); + Map allDetails = new HashMap<>(details); if (serviceOffering.isDynamic()) { allDetails.put(VmDetailConstants.CPU_NUMBER, String.valueOf(serviceOffering.getCpu())); allDetails.put(VmDetailConstants.MEMORY, String.valueOf(serviceOffering.getRamSize())); allDetails.put(VmDetailConstants.CPU_SPEED, String.valueOf(serviceOffering.getSpeed())); } -/* + + // Check disks and supplied disk offerings + List unmanagedInstanceDisks = unmanagedInstance.getDisks(); + if (CollectionUtils.isEmpty(unmanagedInstanceDisks)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("No attached disks found for the unmanaged VM: %s", instanceName)); + } + Pair> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap); + final UnmanagedInstanceTO.Disk rootDisk = rootAndDataDisksPair.first(); + final List dataDisks = rootAndDataDisksPair.second(); + if (rootDisk == null || StringUtils.isEmpty(rootDisk.getController())) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed. Unable to retrieve root disk details for VM: %s ", instanceName)); + } + allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController()); + + // Check NICs and supplied networks + Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); + Map allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner); + if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) { + allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType()); + } + VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; try { - userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner, + userVm = userVmManager.importVM(zone, host, template, "internalCSName", displayName, owner, null, caller, true, null, owner.getAccountId(), userId, - validatedServiceOffering, null, hostName, - cluster.getHypervisorType(), allDetails, powerState); + serviceOffering, null, hostName, + Hypervisor.HypervisorType.KVM, allDetails, powerState); } catch (InsufficientCapacityException ice) { LOGGER.error(String.format("Failed to import vm name: %s", instanceName), ice); throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ice.getMessage()); @@ -1470,6 +1535,7 @@ private UserVm importKvmVirtualMachineInternal(final String instanceName, final if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName)); } + List> diskProfileStoragePoolList = new ArrayList<>(); try { if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { @@ -1484,18 +1550,18 @@ private UserVm importKvmVirtualMachineInternal(final String instanceName, final maxIops = Long.parseLong(details.get("maxIops")); } DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); - diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), + diskProfileStoragePoolList.add(importKvmDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), (rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB), minIops, maxIops, - template, owner, null)); + template, owner, null, remoteUrl)); long deviceId = 1L; for (UnmanagedInstanceTO.Disk disk : dataDisks) { if (disk.getCapacity() == null || disk.getCapacity() == 0) { throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId())); } DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); - diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), + diskProfileStoragePoolList.add(importKvmDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), (disk.getCapacity() / Resource.ResourceType.bytesToGiB), offering.getMinIops(), offering.getMaxIops(), - template, owner, deviceId)); + template, owner, deviceId, remoteUrl)); deviceId++; } } catch (Exception e) { @@ -1508,7 +1574,7 @@ private UserVm importKvmVirtualMachineInternal(final String instanceName, final for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); - importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, forced); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true); nicIndex++; } } catch (Exception e) { @@ -1516,10 +1582,7 @@ private UserVm importKvmVirtualMachineInternal(final String instanceName, final cleanupFailedImportVM(userVm); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); } - if (migrateAllowed) { - userVm = migrateImportedVM(host, template, validatedServiceOffering, userVm, owner, diskProfileStoragePoolList); - } - publishVMUsageUpdateResourceCount(userVm, validatedServiceOffering); */ + publishVMUsageUpdateResourceCount(userVm, serviceOffering); return userVm; } @@ -1559,6 +1622,15 @@ public ListResponse listVmsForImport(ListVmsForImport } GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer; List responses = new ArrayList<>(); + for (String vmName : getRemoteVmsAnswer.getVmNames()) { + UnmanagedInstanceTO instance = new UnmanagedInstanceTO(); + instance.setName(vmName); + if (StringUtils.isNotEmpty(keyword) && + !instance.getName().toLowerCase().contains(keyword)) { + continue; + } + responses.add(createUnmanagedInstanceResponse(instance, null, null)); + } ListResponse listResponses = new ListResponse<>(); listResponses.setResponses(responses, responses.size()); return listResponses; From 2b2f0f17a1e60bd309df4c61e79efbad0cc950a2 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Fri, 1 Sep 2023 12:15:01 +0530 Subject: [PATCH 03/65] Run qemu-img remotely and scp file --- .../agent/api/CopyRemoteVolumeAnswer.java | 12 ++++- .../resource/LibvirtComputingResource.java | 52 ++++++++++++++++--- ...LibvirtCopyRemoteVolumeCommandWrapper.java | 4 +- .../LibvirtGetRemoteVmsCommandWrapper.java | 9 ++-- .../LibvirtComputingResourceTest.java | 10 ---- .../vm/UnmanagedVMsManagerImpl.java | 20 +++++-- 6 files changed, 78 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java index 76f257a9d71e..88787a852ed5 100644 --- a/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java @@ -21,13 +21,15 @@ public class CopyRemoteVolumeAnswer extends Answer { private String remoteIp; + private String filename; CopyRemoteVolumeAnswer() { } - public CopyRemoteVolumeAnswer(CopyRemoteVolumeCommand cmd, String details) { + public CopyRemoteVolumeAnswer(CopyRemoteVolumeCommand cmd, String details, String filename) { super(cmd, true, details); this.remoteIp = cmd.getRemoteIp(); + this.filename = filename; } public String getRemoteIp() { @@ -38,6 +40,14 @@ public void setRemoteIp(String remoteIp) { this.remoteIp = remoteIp; } + public void setFilename(String filename) { + this.filename = filename; + } + + public String getFilename() { + return filename; + } + public String getString() { return "CopyRemoteVolumeAnswer [remoteIp=" + remoteIp + "]"; } 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 5a568882f6a2..414eb01e1886 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 @@ -5260,7 +5260,7 @@ public void setInterfaceDefQueueSettings(Map details, Integer cp } } - public List getStoppedVms(final Connect conn) { + public HashMap getStoppedVms(final Connect conn) { final List stoppedVms = new ArrayList<>(); String[] vms; @@ -5271,12 +5271,12 @@ public List getStoppedVms(final Connect conn) { s_logger.warn("Unable to listDomains", e); return null; } - + + HashMap unmanagedInstances = new HashMap<>(); Domain dm = null; for (String vm : vms) { try { dm = conn.domainLookupByName(vm); - UnmanagedInstanceTO unmanagedInstanceTO = new UnmanagedInstanceTO(); final DomainState ps = dm.getInfo().state; @@ -5286,7 +5286,38 @@ public List getStoppedVms(final Connect conn) { final String vmName = dm.getName(); if (state == PowerState.PowerOff) { - stoppedVms.add(vmName); + UnmanagedInstanceTO unmanagedInstanceTO = new UnmanagedInstanceTO(); + unmanagedInstanceTO.setName(vmName); + unmanagedInstanceTO.setPowerState(UnmanagedInstanceTO.PowerState.PowerOff); + unmanagedInstanceTO.setCpuCores(getCpuShares(dm)); + //unmanagedInstanceTO.setMemory(new Long(dm.getMaxMemory()).intValue()); + unmanagedInstanceTO.setMemory(131072); + unmanagedInstanceTO.setOperatingSystem(dm.getOSType()); + List disks = getDisks(conn, vmName); + List disksTO = new ArrayList<>(); + for(DiskDef disk : disks) { + UnmanagedInstanceTO.Disk diskTO = new UnmanagedInstanceTO.Disk(); + diskTO.setImagePath(disk.getDiskPath()); + diskTO.setLabel(disk.getDiskLabel()); + diskTO.setDatastoreType(disk.getDeviceType().toString()); + s_logger.debug("DiskDef: "+disk); + disksTO.add(diskTO); + } + unmanagedInstanceTO.setDisks(disksTO); + List interfaces = getInterfaces(conn, vmName); + List nicsTO = new ArrayList<>(); + for(InterfaceDef interfaceDef : interfaces) { + UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic(); + nic.setAdapterType(interfaceDef.getModel().toString()); + nic.setMacAddress(interfaceDef.getMacAddress()); + nic.setPciSlot(interfaceDef.getSlot().toString()); + nic.setVlan(interfaceDef.getVlanTag()); + nic.setNetwork(interfaceDef.getBrName()); + s_logger.debug("InterfaceDef: "+interfaceDef); + nicsTO.add(nic); + } + unmanagedInstanceTO.setNics(nicsTO); + unmanagedInstances.put(vmName, unmanagedInstanceTO); } } catch (final LibvirtException e) { s_logger.warn("Unable to get vms", e); @@ -5301,15 +5332,22 @@ public List getStoppedVms(final Connect conn) { } } - return stoppedVms; + return unmanagedInstances; } /* Scp volume from remote host to local directory */ - public void copyVolume(String srcIp, String username, String password, String localDir, String remoteFile) { + public String copyVolume(String srcIp, String username, String password, String localDir, String remoteFile) { try { - SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, remoteFile); + String outputFile = UUID.randomUUID().toString(); + StringBuilder command = new StringBuilder("qemu-img convert "); + command.append(remoteFile); + command.append(" /tmp/"); + command.append(outputFile); + SshHelper.sshExecute(srcIp, 22, username, null, password, command.toString()); + SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, "/tmp/"+outputFile); + return localDir+outputFile; } catch (Exception e) { throw new RuntimeException(e); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java index a35832cddea4..a69946e4dcba 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java @@ -41,9 +41,9 @@ public Answer execute(final CopyRemoteVolumeCommand command, final LibvirtComput String srcFile = command.getSrcFile(); String dstPath = command.getDstPath(); try { - libvirtComputingResource.copyVolume(srcIp, username, password, dstPath, srcFile); + String filename = libvirtComputingResource.copyVolume(srcIp, username, password, dstPath, srcFile); s_logger.debug("Volume Copy Successful "); - return new CopyRemoteVolumeAnswer(command, ""); + return new CopyRemoteVolumeAnswer(command, "", filename); } catch (final Exception e) { s_logger.error("Error while copying file from remote host: "+ e.getMessage()); return new Answer(command, false, result); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index 2fa0472efc4b..a4cb8ac871ea 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -26,11 +26,12 @@ import com.cloud.hypervisor.kvm.resource.LibvirtConnection; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.log4j.Logger; import org.libvirt.Connect; import org.libvirt.LibvirtException; -import java.util.List; +import java.util.HashMap; @ResourceWrapper(handles = GetRemoteVmsCommand.class) public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper { @@ -48,9 +49,9 @@ public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingR String hypervisorURI = sb.toString(); try { Connect conn = LibvirtConnection.getConnection(hypervisorURI); - List vmNames = libvirtComputingResource.getStoppedVms(conn); - s_logger.debug("Vm Names: "+ vmNames.toString()); - return new GetRemoteVmsAnswer(command, "", vmNames); + HashMap vmMap = libvirtComputingResource.getStoppedVms(conn); + s_logger.debug("Found Vms: "+ vmMap.size()); + return new GetRemoteVmsAnswer(command, "", vmMap); } catch (final LibvirtException e) { s_logger.error("Error while listing stopped Vms on remote host: "+ e.getMessage()); return new Answer(command, false, result); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index aa31ea9b9d3e..fc462adc86dc 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -6200,14 +6200,4 @@ public void setupMemoryBalloonStatsPeriodTestSkipVm() throws LibvirtException { Mockito.verify(loggerMock).debug("Skipping the memory balloon stats period setting for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name] because this" + " VM has no memory balloon."); } - - @Test - public void migrateVmDisk() { - String secMnt = "/mnt/sec"; - String srcHost = "10.0.35.222"; - String remoteFile = "/mnt/76de4042-dc63-3837-9eec-c68ee28dca8f/e8d8c3f5-e6c4-4d8a-8248-f2a59563743c"; - String username = "root"; - String password = "P@ssword123"; - libvirtComputingResourceSpy.copyVolume(srcHost, username, password, secMnt, remoteFile); - } } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index cfd01213a1ad..46332780db53 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -42,8 +42,8 @@ import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd; import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd; import org.apache.cloudstack.api.response.ListResponse; -//import org.apache.cloudstack.api.response.NicResponse; -//import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse; +import org.apache.cloudstack.api.response.NicResponse; +import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse; import org.apache.cloudstack.api.response.UnmanagedInstanceResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; @@ -242,7 +242,7 @@ private VMTemplateVO createDefaultDummyVmImportTemplate() { private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) { UnmanagedInstanceResponse response = new UnmanagedInstanceResponse(); response.setName(instance.getName()); - /* + if (cluster != null) { response.setClusterId(cluster.getUuid()); } @@ -298,7 +298,6 @@ private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInsta } } - */ return response; } @@ -1622,6 +1621,17 @@ public ListResponse listVmsForImport(ListVmsForImport } GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer; List responses = new ArrayList<>(); + HashMap vmMap = getRemoteVmsAnswer.getUnmanagedInstances(); + for (String key : vmMap.keySet()) { + UnmanagedInstanceTO instance = vmMap.get(key); + if (StringUtils.isNotEmpty(keyword) && + !instance.getName().toLowerCase().contains(keyword)) { + continue; + } + responses.add(createUnmanagedInstanceResponse(instance, null, null)); + } + + /* for (String vmName : getRemoteVmsAnswer.getVmNames()) { UnmanagedInstanceTO instance = new UnmanagedInstanceTO(); instance.setName(vmName); @@ -1630,7 +1640,7 @@ public ListResponse listVmsForImport(ListVmsForImport continue; } responses.add(createUnmanagedInstanceResponse(instance, null, null)); - } + } */ ListResponse listResponses = new ListResponse<>(); listResponses.setResponses(responses, responses.size()); return listResponses; From 83ab6bc417f4862e56f8a2a1c106ca899b185db8 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Sat, 2 Sep 2023 17:38:29 +0530 Subject: [PATCH 04/65] Fix copy volume --- .../cloudstack/vm/UnmanagedInstanceTO.java | 19 +++ .../service/VolumeOrchestrationService.java | 3 + .../orchestration/VolumeOrchestrator.java | 44 ++++++ .../resource/LibvirtComputingResource.java | 32 ++++- .../kvm/resource/LibvirtDomainXMLParser.java | 77 +++++++++- .../hypervisor/kvm/resource/LibvirtVMDef.java | 18 ++- .../LibvirtGetRemoteVmsCommandWrapper.java | 132 ++++++++++++++++++ .../java/com/cloud/vm/UserVmManagerImpl.java | 10 +- .../vm/UnmanagedVMsManagerImpl.java | 115 ++++++++------- 9 files changed, 394 insertions(+), 56 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java index 95675f2bf349..1ade095667eb 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java @@ -48,6 +48,7 @@ public enum PowerState { private List disks; private List nics; + private String vncPassword; public String getName() { return name; @@ -137,6 +138,14 @@ public void setNics(List nics) { this.nics = nics; } + public String getVncPassword() { + return vncPassword; + } + + public void setVncPassword(String vncPassword) { + this.vncPassword = vncPassword; + } + public static class Disk { private String diskId; @@ -162,6 +171,7 @@ public static class Disk { private String datastorePath; + private int datastorePort; private String datastoreType; public String getDiskId() { @@ -267,6 +277,15 @@ public String getDatastoreType() { public void setDatastoreType(String datastoreType) { this.datastoreType = datastoreType; } + + + public void setDatastorePort(int datastorePort) { + this.datastorePort = datastorePort; + } + + public int getDatastorePort() { + return datastorePort; + } } public static class Nic { diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index 15f5b231be20..01123401faca 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -168,6 +168,9 @@ List allocateTemplatedVolumes(Type type, String name, DiskOffering DiskProfile importVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner, Long deviceId, Long poolId, String path, String chainInfo); + DiskProfile updateImportedVolume(Type type, DiskOffering offering, VirtualMachine vm, VirtualMachineTemplate template, + Long deviceId, Long poolId, String path, String chainInfo, DiskProfile diskProfile); + /** * Unmanage VM volumes */ diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 6f945479bd49..8478a5419018 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -2224,6 +2224,50 @@ public DiskProfile importVolume(Type type, String name, DiskOffering offering, L return toDiskProfile(vol, offering); } + @Override + public DiskProfile updateImportedVolume(Type type, DiskOffering offering, VirtualMachine vm, VirtualMachineTemplate template, + Long deviceId, Long poolId, String path, String chainInfo, DiskProfile diskProfile) { + + VolumeVO vol = _volsDao.findById(diskProfile.getVolumeId()); + if (vm != null) { + vol.setInstanceId(vm.getId()); + } + + if (deviceId != null) { + vol.setDeviceId(deviceId); + } else if (type.equals(Type.ROOT)) { + vol.setDeviceId(0l); + } else { + vol.setDeviceId(1l); + } + + if (template != null) { + if (ImageFormat.ISO.equals(template.getFormat())) { + vol.setIsoId(template.getId()); + } else if (Storage.TemplateType.DATADISK.equals(template.getTemplateType())) { + vol.setTemplateId(template.getId()); + } + if (type == Type.ROOT) { + vol.setTemplateId(template.getId()); + } + } + + // display flag matters only for the User vms + if (VirtualMachine.Type.User.equals(vm.getType())) { + UserVmVO userVm = _userVmDao.findById(vm.getId()); + vol.setDisplayVolume(userVm.isDisplayVm()); + } + + vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType())); + vol.setPoolId(poolId); + vol.setPath(path); + vol.setChainInfo(chainInfo); + vol.setState(Volume.State.Ready); + vol.setAttached(new Date()); + _volsDao.update(vol.getId(), vol); + return toDiskProfile(vol, offering); + } + @Override public void unmanageVolumes(long vmId) { if (s_logger.isDebugEnabled()) { 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 414eb01e1886..dda0403c982b 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 @@ -3698,7 +3698,32 @@ private String getIqn() { } } - protected List getAllVmNames(final Connect conn) { + /** + * Given a disk path on KVM host, attempts to find source host and path using mount command + * @param diskPath KVM host path for virtual disk + * @return Pair with IP of host and path + */ + public Pair getSourceHostPath(String diskPath) { + String sourceHostIp = null, sourcePath = null; + try { + String mountResult = Script.runSimpleBashScript("mount | grep \"" + diskPath + "\""); + s_logger.debug("Got mount result for " + diskPath + "\n\n" + mountResult); + if (StringUtils.isNotEmpty(mountResult)) { + String[] res = mountResult.strip().split(" "); + res = res[0].split(":"); + sourceHostIp = res[0].strip(); + sourcePath = res[1].strip(); + } + if (StringUtils.isNotEmpty(sourceHostIp) && StringUtils.isNotEmpty(sourcePath)) { + return new Pair<>(sourceHostIp, sourcePath); + } + } catch (Exception ex) { + s_logger.warn("Failed to list source host and IP for " + diskPath + ex.toString()); + } + return null; + } + + public List getAllVmNames(final Connect conn) { final ArrayList la = new ArrayList(); try { final String names[] = conn.listDefinedDomains(); @@ -5345,9 +5370,12 @@ public String copyVolume(String srcIp, String username, String password, String command.append(remoteFile); command.append(" /tmp/"); command.append(outputFile); + s_logger.debug("Converting remoteFile: "+remoteFile); SshHelper.sshExecute(srcIp, 22, username, null, password, command.toString()); + s_logger.debug("Copying remoteFile to: "+localDir); SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, "/tmp/"+outputFile); - return localDir+outputFile; + s_logger.debug("Successfully copyied remoteFile to: "+localDir+"/"+outputFile); + return localDir+"/"+outputFile; } catch (Exception e) { throw new RuntimeException(e); } 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 a5565c2de34e..5b27af75518c 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 @@ -57,7 +57,11 @@ public class LibvirtDomainXMLParser { private final List channels = new ArrayList(); private final List watchDogDefs = new ArrayList(); private Integer vncPort; + private String vncPasswd; private String desc; + private LibvirtVMDef.CpuTuneDef cpuTuneDef; + + private LibvirtVMDef.CpuModeDef cpuModeDef; private String name; @@ -305,6 +309,12 @@ public boolean parseDomainXML(String domXML) { vncPort = null; } } + + String passwd = graphic.getAttribute("passwd"); + if (passwd != null) { + vncPasswd = passwd; + } + } NodeList rngs = devices.getElementsByTagName("rng"); @@ -350,7 +360,8 @@ public boolean parseDomainXML(String domXML) { watchDogDefs.add(def); } - + extractCpuTuneDef(rootElement); + extractCpuModeDef(rootElement); return true; } catch (ParserConfigurationException e) { s_logger.debug(e.toString()); @@ -411,6 +422,9 @@ public List getInterfaces() { return interfaces; } + public String getVncPasswd() { + return vncPasswd; + } public MemBalloonDef getMemBalloon() { return memBalloonDef; } @@ -438,4 +452,65 @@ public String getDescription() { public String getName() { return name; } + + public LibvirtVMDef.CpuTuneDef getCpuTuneDef() { + return cpuTuneDef; + } + + public LibvirtVMDef.CpuModeDef getCpuModeDef() { + return cpuModeDef; + } + + private void extractCpuTuneDef(final Element rootElement) { + NodeList cpuTunesList = rootElement.getElementsByTagName("cputune"); + if (cpuTunesList.getLength() > 0) { + cpuTuneDef = new LibvirtVMDef.CpuTuneDef(); + final Element cpuTuneDefElement = (Element) cpuTunesList.item(0); + final String cpuShares = getTagValue("shares", cpuTuneDefElement); + if (StringUtils.isNotBlank(cpuShares)) { + cpuTuneDef.setShares((Integer.parseInt(cpuShares))); + } + + final String quota = getTagValue("quota", cpuTuneDefElement); + if (StringUtils.isNotBlank(quota)) { + cpuTuneDef.setQuota((Integer.parseInt(quota))); + } + + final String period = getTagValue("period", cpuTuneDefElement); + if (StringUtils.isNotBlank(period)) { + cpuTuneDef.setPeriod((Integer.parseInt(period))); + } + } + } + + private void extractCpuModeDef(final Element rootElement){ + NodeList cpuModeList = rootElement.getElementsByTagName("cpu"); + if (cpuModeList.getLength() > 0){ + cpuModeDef = new LibvirtVMDef.CpuModeDef(); + final Element cpuModeDefElement = (Element) cpuModeList.item(0); + final String cpuModel = getTagValue("model", cpuModeDefElement); + if (StringUtils.isNotBlank(cpuModel)){ + cpuModeDef.setModel(cpuModel); + } + NodeList cpuFeatures = cpuModeDefElement.getElementsByTagName("features"); + if (cpuFeatures.getLength() > 0) { + final ArrayList features = new ArrayList<>(cpuFeatures.getLength()); + for (int i = 0; i < cpuFeatures.getLength(); i++) { + final Element feature = (Element)cpuFeatures.item(i); + final String policy = feature.getAttribute("policy"); + String featureName = feature.getAttribute("name"); + if ("disable".equals(policy)) { + featureName = "-" + featureName; + } + features.add(featureName); + } + cpuModeDef.setFeatures(features); + } + final String sockets = getAttrValue("topology", "sockets", cpuModeDefElement); + final String cores = getAttrValue("topology", "cores", cpuModeDefElement); + if (StringUtils.isNotBlank(sockets) && StringUtils.isNotBlank(cores)) { + cpuModeDef.setTopology(Integer.parseInt(cores), Integer.parseInt(sockets)); + } + } + } } 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 aac44fc14193..19a830ff6e01 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 @@ -609,7 +609,7 @@ public String toString() { } } - enum DiskType { + public enum DiskType { FILE("file"), BLOCK("block"), DIRECTROY("dir"), NETWORK("network"); String _diskType; @@ -1072,6 +1072,18 @@ public void setSerial(String serial) { public LibvirtDiskEncryptDetails getLibvirtDiskEncryptDetails() { return this.encryptDetails; } + public String getSourceHost() { + return _sourceHost; + } + + public int getSourceHostPort() { + return _sourcePort; + } + + public String getSourcePath() { + return _sourcePath; + } + @Override public String toString() { StringBuilder diskBuilder = new StringBuilder(); @@ -1737,6 +1749,10 @@ public String toString() { modeBuilder.append(""); return modeBuilder.toString(); } + + public int getCoresPerSocket() { + return _coresPerSocket; + } } public static class SerialDef { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index a4cb8ac871ea..de4bbcbd32ae 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -24,14 +24,26 @@ import com.cloud.agent.api.GetRemoteVmsCommand; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.hypervisor.kvm.resource.LibvirtConnection; +import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.log4j.Logger; import org.libvirt.Connect; +import org.libvirt.Domain; import org.libvirt.LibvirtException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; @ResourceWrapper(handles = GetRemoteVmsCommand.class) public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper { @@ -47,8 +59,21 @@ public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingR sb.append(command.getRemoteIp()); sb.append("/system"); String hypervisorURI = sb.toString(); + HashMap unmanagedInstances = new HashMap<>(); try { Connect conn = LibvirtConnection.getConnection(hypervisorURI); + final List domains = new ArrayList<>(); + final List allVmNames = libvirtComputingResource.getAllVmNames(conn); + for (String name : allVmNames) { + final Domain domain = libvirtComputingResource.getDomain(conn, name); + domains.add(domain); + } + + for (Domain domain : domains) { + UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn); + unmanagedInstances.put(instance.getName(), instance); + domain.free(); + } HashMap vmMap = libvirtComputingResource.getStoppedVms(conn); s_logger.debug("Found Vms: "+ vmMap.size()); return new GetRemoteVmsAnswer(command, "", vmMap); @@ -58,4 +83,111 @@ public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingR } } + private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvirtComputingResource, Domain domain, Connect conn) { + try { + final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); + parser.parseDomainXML(domain.getXMLDesc(1)); + + final UnmanagedInstanceTO instance = new UnmanagedInstanceTO(); + instance.setName(domain.getName()); + //instance.setCpuCores((int) LibvirtComputingResource.countDomainRunningVcpus(domain)); + if (parser.getCpuModeDef() != null) { + instance.setCpuCoresPerSocket(parser.getCpuModeDef().getCoresPerSocket()); + } + instance.setCpuSpeed(parser.getCpuTuneDef().getShares()); + instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName()))); + //instance.setMemory((int) LibvirtComputingResource.getDomainMemory(domain) / 1024); + instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces())); + instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource)); + instance.setVncPassword(parser.getVncPasswd() + "aaaaaaaaaaaaaa"); // Suffix back extra characters for DB compatibility + + return instance; + } catch (Exception e) { + s_logger.info("Unable to retrieve unmanaged instance info. " + e.getMessage()); + throw new CloudRuntimeException("Unable to retrieve unmanaged instance info. " + e.getMessage()); + } + } + + private UnmanagedInstanceTO.PowerState getPowerState(VirtualMachine.PowerState vmPowerState) { + switch (vmPowerState) { + case PowerOn: + return UnmanagedInstanceTO.PowerState.PowerOn; + case PowerOff: + return UnmanagedInstanceTO.PowerState.PowerOff; + default: + return UnmanagedInstanceTO.PowerState.PowerUnknown; + + } + } + + private List getUnmanagedInstanceNics(List interfaces) { + final ArrayList nics = new ArrayList<>(interfaces.size()); + int counter = 0; + for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) { + final UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic(); + nic.setNicId(String.valueOf(counter++)); + nic.setMacAddress(interfaceDef.getMacAddress()); + nic.setAdapterType(interfaceDef.getModel().toString()); + nic.setNetwork(interfaceDef.getDevName()); + nic.setPciSlot(interfaceDef.getSlot().toString()); + nic.setVlan(interfaceDef.getVlanTag()); + nics.add(nic); + } + return nics; + } + + private List getUnmanagedInstanceDisks(List disksInfo, LibvirtComputingResource libvirtComputingResource){ + final ArrayList disks = new ArrayList<>(disksInfo.size()); + int counter = 0; + for (LibvirtVMDef.DiskDef diskDef : disksInfo) { + if (diskDef.getDeviceType() != LibvirtVMDef.DiskDef.DeviceType.DISK) { + continue; + } + + final UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk(); + Long size = null; + String imagePath = null; + try { + QemuImgFile file = new QemuImgFile(diskDef.getSourcePath()); + QemuImg qemu = new QemuImg(0); + Map info = qemu.info(file); + size = Long.parseLong(info.getOrDefault("virtual_size", "0")); + imagePath = info.getOrDefault("image", null); + } catch (QemuImgException | LibvirtException e) { + throw new RuntimeException(e); + } + + disk.setPosition(counter); + disk.setCapacity(size); + disk.setDiskId(String.valueOf(counter++)); + disk.setLabel(diskDef.getDiskLabel()); + disk.setController(diskDef.getBusType().toString()); + + + Pair sourceHostPath = getSourceHostPath(libvirtComputingResource, diskDef.getSourcePath()); + if (sourceHostPath != null) { + disk.setDatastoreHost(sourceHostPath.first()); + disk.setDatastorePath(sourceHostPath.second()); + } else { + disk.setDatastorePath(diskDef.getSourcePath()); + disk.setDatastoreHost(diskDef.getSourceHost()); + } + + disk.setDatastoreType(diskDef.getDiskType().toString()); + disk.setDatastorePort(diskDef.getSourceHostPort()); + disk.setImagePath(imagePath); + disk.setDatastoreName(imagePath.substring(imagePath.lastIndexOf("/"))); + disks.add(disk); + } + return disks; + } + + private Pair getSourceHostPath(LibvirtComputingResource libvirtComputingResource, String diskPath) { + int pathEnd = diskPath.lastIndexOf("/"); + if (pathEnd >= 0) { + diskPath = diskPath.substring(0, pathEnd); + return libvirtComputingResource.getSourceHostPath(diskPath); + } + return null; + } } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 824dac0d5671..d042aaee7622 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4489,7 +4489,9 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin if (isImport) { vm.setDataCenterId(zone.getId()); - vm.setHostId(host.getId()); + if(host != null) { + vm.setHostId(host.getId()); + } if (lastHost != null) { vm.setLastHostId(lastHost.getId()); } @@ -8204,9 +8206,9 @@ public UserVm importVM(final DataCenter zone, final Host host, final VirtualMach if (zone == null) { throw new InvalidParameterValueException("Unable to import virtual machine with invalid zone"); } - if (host == null) { - throw new InvalidParameterValueException("Unable to import virtual machine with invalid host"); - } + // if (host == null) { + // throw new InvalidParameterValueException("Unable to import virtual machine with invalid host"); + // } final long id = _vmDao.getNextInSequence(Long.class, "id"); diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 46332780db53..9ffb63d0ccae 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -503,7 +503,8 @@ private StoragePool getStoragePool(final UnmanagedInstanceTO.Disk disk, final Da private Pair> getRootAndDataDisks(List disks, final Map dataDiskOfferingMap) { UnmanagedInstanceTO.Disk rootDisk = null; List dataDisks = new ArrayList<>(); - if (disks.size() == 1) { + //ToDo: fix cdrom + if (disks.size() == 2) { rootDisk = disks.get(0); return new Pair<>(rootDisk, dataDisks); } @@ -673,6 +674,8 @@ private Map getUnmanagedNicNetworkMap(String instanceName, List getUnmanagedNicNetworkMap(String instanceName, List importKvmDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering, - Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template, - Account owner, Long deviceId, String remoteUrl) { - final DataCenter zone = dataCenterDao.findById(vm.getDataCenterId()); + private Pair importKvmDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, DeployDestination dest, DiskOffering diskOffering, + Volume.Type type, VirtualMachineTemplate template, + Long deviceId, String remoteUrl, DiskProfile diskProfile) { final String path = StringUtils.isEmpty(disk.getFileBaseName()) ? disk.getImagePath() : disk.getFileBaseName(); String chainInfo = disk.getChainInfo(); if (StringUtils.isEmpty(chainInfo)) { @@ -693,15 +695,16 @@ private Pair importKvmDisk(UnmanagedInstanceTO.Disk di diskInfo.setDiskChain(new String[]{disk.getImagePath()}); chainInfo = gson.toJson(diskInfo); } - StoragePool storagePool = getStoragePool(disk, zone, cluster); + Map storage = dest.getStorageForDisks(); + Volume volume = volumeDao.findById(diskProfile.getVolumeId()); + StoragePool storagePool = storage.get(volume); CopyRemoteVolumeCommand copyRemoteVolumeCommand = new CopyRemoteVolumeCommand(); copyRemoteVolumeCommand.setRemoteIp(remoteUrl); copyRemoteVolumeCommand.setUsername("root"); copyRemoteVolumeCommand.setPassword("P@ssword123"); - copyRemoteVolumeCommand.setSrcFile(disk.getFileBaseName()); - copyRemoteVolumeCommand.setDstPath(storagePool.getPath()); - Long hostId = vm.getHostId(); - Answer answer = agentManager.easySend(hostId, copyRemoteVolumeCommand); + copyRemoteVolumeCommand.setSrcFile(path); + copyRemoteVolumeCommand.setDstPath("/mnt/76de4042-dc63-3837-9eec-c68ee28dca8f"); + Answer answer = agentManager.easySend(dest.getHost().getId(), copyRemoteVolumeCommand); if (!(answer instanceof CopyRemoteVolumeAnswer)) { throw new CloudRuntimeException("Error while copying volume"); } @@ -709,8 +712,8 @@ private Pair importKvmDisk(UnmanagedInstanceTO.Disk di if(!copyRemoteVolumeAnswer.getResult()) { throw new CloudRuntimeException("Error while copying volume"); } - DiskProfile profile = volumeManager.importVolume(type, name, diskOffering, diskSize, - minIops, maxIops, vm, template, owner, deviceId, storagePool.getId(), path, chainInfo); + DiskProfile profile = volumeManager.updateImportedVolume(type, diskOffering, vm, template, deviceId, + storagePool.getId(), copyRemoteVolumeAnswer.getFilename(), chainInfo, diskProfile); return new Pair<>(profile, storagePool); } @@ -1467,8 +1470,13 @@ public UserVmResponse importVm(ImportVmCmd cmd) { final Map nicIpAddressMap = cmd.getNicIpAddressList(); final Map dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList(); final Map details = cmd.getDetails(); - UnmanagedInstanceTO unmanagedInstanceTO = new UnmanagedInstanceTO(); + String remoteUrl = cmd.getUrl(); + HashMap instancesMap = getRemoteVms(zoneId, remoteUrl, cmd.getUsername(), cmd.getPassword()); + UnmanagedInstanceTO unmanagedInstanceTO = instancesMap.get(cmd.getName()); + if(unmanagedInstanceTO == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Vm with name: %s not found on remote host", instanceName)); + } UserVm userVm = importKvmVirtualMachineInternal(unmanagedInstanceTO, instanceName, zone, template, displayName, hostName, caller, owner, userId, @@ -1490,10 +1498,6 @@ private UserVm importKvmVirtualMachineInternal(final UnmanagedInstanceTO unmanag final String remoteUrl, final Map details) { UserVm userVm = null; - long hostId = 6; - HostVO host = hostDao.findById(hostId); - Cluster cluster = clusterDao.findById(host.getClusterId()); - Map allDetails = new HashMap<>(details); if (serviceOffering.isDynamic()) { allDetails.put(VmDetailConstants.CPU_NUMBER, String.valueOf(serviceOffering.getCpu())); @@ -1508,11 +1512,13 @@ private UserVm importKvmVirtualMachineInternal(final UnmanagedInstanceTO unmanag } Pair> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap); final UnmanagedInstanceTO.Disk rootDisk = rootAndDataDisksPair.first(); + rootDisk.setCapacity(1000l); final List dataDisks = rootAndDataDisksPair.second(); - if (rootDisk == null || StringUtils.isEmpty(rootDisk.getController())) { + /*if (rootDisk == null || StringUtils.isEmpty(rootDisk.getController())) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed. Unable to retrieve root disk details for VM: %s ", instanceName)); } - allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController()); + allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController());*/ + allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER,"0"); // Check NICs and supplied networks Map nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap); @@ -1523,7 +1529,7 @@ private UserVm importKvmVirtualMachineInternal(final UnmanagedInstanceTO unmanag VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff; try { - userVm = userVmManager.importVM(zone, host, template, "internalCSName", displayName, owner, + userVm = userVmManager.importVM(zone, null, template, "internalCSName", displayName, owner, null, caller, true, null, owner.getAccountId(), userId, serviceOffering, null, hostName, Hypervisor.HypervisorType.KVM, allDetails, powerState); @@ -1534,35 +1540,44 @@ private UserVm importKvmVirtualMachineInternal(final UnmanagedInstanceTO unmanag if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName)); } + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + String rootVolumeName = String.format("ROOT-%s", userVm.getId()); + DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, rootDisk.getCapacity(), null, null, userVm, template, owner, null); + + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm, template, serviceOffering, owner, null); + DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList(); + final DataCenterDeployment plan = new DataCenterDeployment(zone.getId(), null, null, null, null, null); + DeployDestination dest = null; + try { + dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null); + } catch (Exception e) { + LOGGER.warn(String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName()), e); + cleanupFailedImportVM(userVm); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName())); + } + if(dest == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName())); + } List> diskProfileStoragePoolList = new ArrayList<>(); try { if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) { throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); } - Long minIops = null; - if (details.containsKey("minIops")) { - minIops = Long.parseLong(details.get("minIops")); - } - Long maxIops = null; - if (details.containsKey("maxIops")) { - maxIops = Long.parseLong(details.get("maxIops")); - } - DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); - diskProfileStoragePoolList.add(importKvmDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), - (rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB), minIops, maxIops, - template, owner, null, remoteUrl)); + + diskProfileStoragePoolList.add(importKvmDisk(rootDisk, userVm, dest, diskOffering, Volume.Type.ROOT, + template, null, remoteUrl, diskProfile)); long deviceId = 1L; - for (UnmanagedInstanceTO.Disk disk : dataDisks) { + /* for (UnmanagedInstanceTO.Disk disk : dataDisks) { if (disk.getCapacity() == null || disk.getCapacity() == 0) { throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId())); } DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId())); - diskProfileStoragePoolList.add(importKvmDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), + diskProfileStoragePoolList.add(importKvmDisk(disk, userVm, dest.getCluster(), offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), (disk.getCapacity() / Resource.ResourceType.bytesToGiB), offering.getMinIops(), offering.getMaxIops(), - template, owner, deviceId, remoteUrl)); + template, owner, deviceId, dest.getHost().getId(), remoteUrl)); deviceId++; - } + }*/ } catch (Exception e) { LOGGER.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e); cleanupFailedImportVM(userVm); @@ -1609,19 +1624,8 @@ public ListResponse listVmsForImport(ListVmsForImport keyword = keyword.toLowerCase(); } - List hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.KVM, zoneId); - if(hosts.size() < 1) { - throw new CloudRuntimeException("No hosts available for Vm Import"); - } - HostVO host = hosts.get(0); - GetRemoteVmsCommand getRemoteVmsCommand = new GetRemoteVmsCommand(cmd.getUrl(), cmd.getUsername(), cmd.getPassword()); - Answer answer = agentManager.easySend(host.getId(), getRemoteVmsCommand); - if (!(answer instanceof GetRemoteVmsAnswer)) { - throw new CloudRuntimeException("Error while listing remote Vms"); - } - GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer; List responses = new ArrayList<>(); - HashMap vmMap = getRemoteVmsAnswer.getUnmanagedInstances(); + HashMap vmMap = getRemoteVms(zoneId, cmd.getUrl(), cmd.getUsername(), cmd.getPassword()); for (String key : vmMap.keySet()) { UnmanagedInstanceTO instance = vmMap.get(key); if (StringUtils.isNotEmpty(keyword) && @@ -1646,6 +1650,21 @@ public ListResponse listVmsForImport(ListVmsForImport return listResponses; } + private HashMap getRemoteVms(long zoneId, String remoteUrl, String username, String password) { + //ToDo: add option to list one Vm by name + List hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.KVM, zoneId); + if(hosts.size() < 1) { + throw new CloudRuntimeException("No hosts available for Vm Import"); + } + HostVO host = hosts.get(0); + GetRemoteVmsCommand getRemoteVmsCommand = new GetRemoteVmsCommand(remoteUrl, username, password); + Answer answer = agentManager.easySend(host.getId(), getRemoteVmsCommand); + if (!(answer instanceof GetRemoteVmsAnswer)) { + throw new CloudRuntimeException("Error while listing remote Vms"); + } + GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer; + return getRemoteVmsAnswer.getUnmanagedInstances(); + } @Override public String getConfigComponentName() { From 1b7eba73cf347837c2ae0a1aa60d1f76803d519d Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Thu, 14 Sep 2023 14:29:00 +0530 Subject: [PATCH 05/65] UI changes to include multiple KVM migration options --- ui/public/locales/en.json | 16 + ui/src/config/section/tools.js | 9 + .../compute/wizard/MultiNetworkSelection.vue | 6 - .../views/tools/ImportUnmanagedInstance.vue | 9 +- ui/src/views/tools/ManageInstances.vue | 290 ++++++++++++++++-- 5 files changed, 300 insertions(+), 30 deletions(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 199437c80336..92fa3d887804 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -135,7 +135,12 @@ "label.action.health.monitor": "Health monitor", "label.action.image.store.read.only": "Make image store read-only", "label.action.image.store.read.write": "Make image store read-write", +<<<<<<< HEAD "label.action.import.export.instances": "Import-Export Instances", +======= +"label.action.import.export.instances": "Import-Export instances", +"label.action.ingest.instances": "Ingest instances", +>>>>>>> f4a3c46a25 (UI changes to include multiple KVM migration options) "label.action.iso.permission": "Update ISO permissions", "label.action.iso.share": "Update ISO sharing", "label.action.lock.account": "Lock Account", @@ -667,7 +672,12 @@ "label.deployasis": "Read Instance settings from OVA", "label.deploymentplanner": "Deployment planner", "label.desc.db.stats": "Database Statistics", +<<<<<<< HEAD "label.desc.importexportinstancewizard": "Import and export Instances to/from an existing VMware cluster.", +======= +"label.desc.importexportinstancewizard": "Import and export instances to/from an existing VMware cluster.", +"label.desc.ingesttinstancewizard": "Ingest instances from an external KVM host", +>>>>>>> f4a3c46a25 (UI changes to include multiple KVM migration options) "label.desc.usage.stats": "Usage Server Statistics", "label.description": "Description", "label.destaddressgroupuuid": "Destination Address Group", @@ -706,6 +716,7 @@ "label.disconnected": "Last disconnected", "label.disk": "Disk", "label.disk.offerings": "Disk offerings", +"label.disk.path": "Disk Path", "label.disk.selection": "Disk selection", "label.disk.size": "Disk size", "label.disk.usage.info": "Disk usage information", @@ -863,6 +874,7 @@ "label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.", "label.failed": "Failed", "label.featured": "Featured", +"label.fetch.instances": "Fetch Instances", "label.fetch.latest": "Fetch latest", "label.files": "Alternate files to retrieve", "label.filter": "Filter", @@ -1000,6 +1012,7 @@ "label.info": "Info", "label.info.upper": "INFO", "label.infrastructure": "Infrastructure", +"label.ingest.instance": "Ingest Instance", "label.ingress": "Ingress", "label.ingress.rule": "Ingress Rule", "label.initial": "Inital", @@ -1643,6 +1656,7 @@ "label.release.dedicated.pod": "Release dedicated pod", "label.release.dedicated.zone": "Release dedicated zone", "label.releasing.ip": "Releasing IP", +"label.remote.instances": "Remote Instances", "label.remove": "Remove", "label.remove.annotation": "Remove comment", "label.remove.egress.rule": "Remove egress rule", @@ -1859,6 +1873,7 @@ "label.snapshottype": "Snapshot Type", "label.sockettimeout": "Socket timeout", "label.softwareversion": "Software version", +"label.source": "Source", "label.source.based": "SourceBased", "label.sourcecidr": "Source CIDR", "label.sourcehost": "Source host", @@ -2628,6 +2643,7 @@ "message.desc.created.ssh.key.pair": "Created a SSH key pair.", "message.desc.host": "Each cluster must contain at least one host (computer) for guest Instances to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.

Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.", "message.desc.importexportinstancewizard": "This feature only applies Cloudstack VMware clusters. By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. The Instance is left running and not physically moved. Unmanaging Instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", +"message.desc.importingestinstancewizard": "This feature only applies to libvirt based KVM instances. Only Stopped instances can be ingested", "message.desc.primary.storage": "Each cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the Instances running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.", "message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this Instance.", "message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.

Provide the IP address and exported path.", diff --git a/ui/src/config/section/tools.js b/ui/src/config/section/tools.js index a04563c477a4..6237f73a59d0 100644 --- a/ui/src/config/section/tools.js +++ b/ui/src/config/section/tools.js @@ -68,6 +68,15 @@ export default { resourceType: 'UserVm', permission: ['listInfrastructure', 'listUnmanagedInstances'], component: () => import('@/views/tools/ManageInstances.vue') + }, + { + name: 'ingestinstances', + title: 'label.action.ingest.instances', + icon: 'interaction-outlined', + docHelp: 'adminguide/virtual_machines.html#ingest-virtual-machine', + resourceType: 'UserVm', + permission: ['listVmsForImport'], + component: () => import('@/views/tools/IngestInstances.vue') } ] } diff --git a/ui/src/views/compute/wizard/MultiNetworkSelection.vue b/ui/src/views/compute/wizard/MultiNetworkSelection.vue index f2397d5c48c0..e26961857315 100644 --- a/ui/src/views/compute/wizard/MultiNetworkSelection.vue +++ b/ui/src/views/compute/wizard/MultiNetworkSelection.vue @@ -197,12 +197,6 @@ export default { this.validNetworks = {} for (const item of this.items) { this.validNetworks[item.id] = this.networks - if (this.filterUnimplementedNetworks) { - this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => (x.state === 'Implemented' || (x.state === 'Setup' && ['Shared', 'L2'].includes(x.type)))) - } - if (this.filterMatchKey) { - this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => x[this.filterMatchKey] === item[this.filterMatchKey]) - } } this.setDefaultValues() this.loading = false diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index f02541272a9d..55da496b374a 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -678,8 +678,13 @@ export default { const params = { name: this.resource.name, clusterid: this.cluster.id, - displayname: values.displayname + displayname: values.displayname, + zoneid: 1 } + params.url = '10.0.34.170' + params.username = 'root' + params.password = 'P@ssword123' + params.hypervisor = 'KVM' if (!this.computeOffering || !this.computeOffering.id) { this.$notification.error({ message: this.$t('message.request.failed'), @@ -772,7 +777,7 @@ export default { } this.updateLoading(true) const name = this.resource.name - api('importUnmanagedInstance', params).then(json => { + api('importVm', params).then(json => { const jobId = json.importunmanagedinstanceresponse.jobid this.$pollJob({ jobId, diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 460ff91b38b5..eec23d0f4ce7 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -37,7 +37,7 @@ :md="24">
- +
+ + + + + + {{ hv }} + + + + + + + + + + + {{ kvmoption.label }} + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {{ $t('label.fetch.instances') }} + +
+
- + -
- - - {{ $t('label.import.instance') }} - -
+
+ + + {{ $t('label.import.instance') }} + +
- + + + + @@ -438,7 +438,7 @@ export default { selectedDomainId: null, templates: [], templateLoading: false, - templateType: 'auto', + templateType: this.defaultTemplateType(), totalComputeOfferings: 0, computeOfferings: [], computeOfferingLoading: false, @@ -802,6 +802,12 @@ export default { updateMultiNetworkOffering (data) { this.nicsNetworksMapping = data }, + defaultTemplateType () { + if (this.cluster.hypervisortype === 'VMWare') { + return 'auto' + } + return 'custom' + }, changeTemplateType (e) { this.templateType = e.target.value if (this.templateType === 'auto') { @@ -1109,7 +1115,7 @@ export default { for (var field of fields) { this.updateFieldValue(field, undefined) } - this.templateType = 'auto' + this.templateType = this.defaultTemplateType() this.updateComputeOffering(undefined) this.switches = {} }, diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index ff291300eee3..3faa4de6b23e 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -418,6 +418,7 @@
@@ -452,16 +453,17 @@ :importsource="selectedSourceAction" :zoneid="this.zoneId" :hypervisor="this.destinationHypervisor" - :exthost="this.values.hostname" - :username="this.values.username" - :password="this.values.password" - :tmppath="this.values.tmppath" - :diskpath="this.values.diskpath" + :exthost="this.values?.hostname || ''" + :username="this.values?.username || ''" + :password="this.values?.password || ''" + :tmppath="this.values?.tmppath || ''" + :diskpath="this.values?.diskpath || ''" :isOpen="showUnmanageForm" :selectedVmwareVcenter="selectedVmwareVcenter" @refresh-data="fetchInstances" @close-action="closeImportUnmanagedInstanceForm" @loading-changed="updateManageInstanceActionLoading" + @track-import-jobid="trackImportJobId" />
@@ -497,7 +499,8 @@ export default { name: 'unmanaged', label: 'Manage/Unmanage existing instances', sourceDestHypervisors: { - vmware: 'vmware' + vmware: 'vmware', + kvm: 'kvm' }, wizardTitle: this.$t('label.desc.importexportinstancewizard'), wizardDescription: this.$t('message.desc.importexportinstancewizard') @@ -1218,6 +1221,9 @@ export default { }, updateManageInstanceActionLoading (value) { this.importUnmanagedInstanceLoading = value + if (!value) { + this.fetchInstances() + } }, onManageInstanceAction () { this.selectedUnmanagedInstance = {} @@ -1284,6 +1290,21 @@ export default { } }) }, + trackImportJobId (details) { + const jobId = details[0] + const name = details[1] + this.$pollJob({ + jobId, + title: this.$t('label.import.instance'), + description: this.$t('label.import.instance'), + loadingMessage: `${this.$t('label.import.instance')} ${name} ${this.$t('label.in.progress')}`, + catchMessage: this.$t('error.fetching.async.job.result'), + successMessage: this.$t('message.success.import.instance') + ' ' + name, + successMethod: (result) => { + this.fetchInstances() + } + }) + }, unmanageInstances () { for (var index of this.managedInstancesSelectedRowKeys) { const vm = this.managedInstances[index] From f89df4cb64f952760e4a776542d0bc76a3b7d5c7 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 9 Nov 2023 01:53:49 -0300 Subject: [PATCH 32/65] Exclude unmanaged instances that cannot be parsed from the listing --- .../LibvirtGetUnmanagedInstancesCommandWrapper.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java index d59a5e9ff4dc..78877ee0be0d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java @@ -58,8 +58,10 @@ public GetUnmanagedInstancesAnswer execute(GetUnmanagedInstancesCommand command, for (Domain domain : domains) { UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn); - unmanagedInstances.put(instance.getName(), instance); - domain.free(); + if (instance != null) { + unmanagedInstances.put(instance.getName(), instance); + domain.free(); + } } } catch (Exception e) { LOGGER.error("GetUnmanagedInstancesCommand failed due to " + e.getMessage()); @@ -133,8 +135,8 @@ private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvir return instance; } catch (Exception e) { - LOGGER.info("Unable to retrieve unmanaged instance info. " + e.getMessage()); - throw new CloudRuntimeException("Unable to retrieve unmanaged instance info. " + e.getMessage()); + LOGGER.info("Unable to retrieve unmanaged instance info. " + e.getMessage(), e); + return null; } } From babd2c571c5f12bfac4fb957ecd41517257985ea Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Thu, 9 Nov 2023 12:14:33 +0530 Subject: [PATCH 33/65] Fix vlan check for L2 networks. UI validations --- ui/public/locales/en.json | 1 + .../compute/wizard/MultiNetworkSelection.vue | 8 +++++- .../views/tools/ImportUnmanagedInstance.vue | 25 ++++++++++++++++++- ui/src/views/tools/ManageInstances.vue | 13 ++++++++-- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 29e25c352cc1..dc4bd9ad8e00 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -710,6 +710,7 @@ "label.disk": "Disk", "label.disk.offerings": "Disk offerings", "label.disk.path": "Disk Path", +"label.disk.path.tooltip": "Disk Path relative to the selected Storage Pool", "label.disk.selection": "Disk selection", "label.disk.size": "Disk size", "label.disk.usage.info": "Disk usage information", diff --git a/ui/src/views/compute/wizard/MultiNetworkSelection.vue b/ui/src/views/compute/wizard/MultiNetworkSelection.vue index d56a4ecd24d1..6fce7015e909 100644 --- a/ui/src/views/compute/wizard/MultiNetworkSelection.vue +++ b/ui/src/views/compute/wizard/MultiNetworkSelection.vue @@ -201,6 +201,12 @@ export default { this.validNetworks = {} for (const item of this.items) { this.validNetworks[item.id] = this.networks + if (this.filterUnimplementedNetworks) { + this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => (x.state === 'Implemented' || (x.state === 'Setup' && ['Shared', 'L2'].includes(x.type)))) + } + if (this.filterMatchKey) { + this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => x[this.filterMatchKey] === item[this.filterMatchKey]) + } } this.setDefaultValues() this.loading = false @@ -229,6 +235,7 @@ export default { }, handleNetworkChange (nic, networkId) { this.setIpAddressEnabled(nic, _.find(this.validNetworks[nic.id], (option) => option.id === networkId)) + this.values[nic.id] = networkId this.sendValuesTimed() }, sendValuesTimed () { @@ -255,7 +262,6 @@ export default { } data[x] = d } - d.network = this.networks[x].id } this.$emit('select-multi-network', data) } diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index f3499aa885be..14d413481a73 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -954,7 +954,6 @@ export default { params.networkid = values.networkid } } - console.log(importapi) if (!this.computeOffering || !this.computeOffering.id) { this.$notification.error({ message: this.$t('message.request.failed'), @@ -997,6 +996,16 @@ export default { }) } } + if (this.isDiskImport) { + var storageType = this.computeOffering.storagetype + if (this.importsource !== storageType) { + this.$notification.error({ + message: this.$t('message.request.failed'), + description: 'Incompatible Storage. Import Source is: ' + this.importsource + '. Storage Type in service offering is: ' + storageType + }) + return + } + } if (this.selectedVmwareVcenter) { if (this.selectedVmwareVcenter.existingvcenterid) { params.existingvcenterid = this.selectedVmwareVcenter.existingvcenterid @@ -1039,6 +1048,7 @@ export default { } var nicNetworkIndex = 0 var nicIpIndex = 0 + var networkcheck = new Set() for (var nicId in this.nicsNetworksMapping) { if (!this.nicsNetworksMapping[nicId].network) { this.$notification.error({ @@ -1049,6 +1059,16 @@ export default { } params['nicnetworklist[' + nicNetworkIndex + '].nic'] = nicId params['nicnetworklist[' + nicNetworkIndex + '].network'] = this.nicsNetworksMapping[nicId].network + var netId = this.nicsNetworksMapping[nicId].network + if (!networkcheck.has(netId)) { + networkcheck.add(netId) + } else { + this.$notification.error({ + message: this.$t('message.request.failed'), + description: 'Same network cannot be assigned to multiple Nics' + }) + return + } nicNetworkIndex++ if ('ipAddress' in this.nicsNetworksMapping[nicId]) { if (!this.nicsNetworksMapping[nicId].ipAddress) { @@ -1063,6 +1083,9 @@ export default { nicIpIndex++ } } + if (this.isExternalImport) { + return + } this.updateLoading(true) const name = params.name return new Promise((resolve, reject) => { diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 3faa4de6b23e..8eb8b8ea94f4 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -241,12 +241,19 @@ + ref="diskpath"> + +
Date: Thu, 9 Nov 2023 14:21:08 +0530 Subject: [PATCH 34/65] Added tooltips to import form --- ui/public/locales/en.json | 12 +++++ ui/src/views/tools/ManageInstances.vue | 71 +++++++++++++++++--------- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index dc4bd9ad8e00..ba63ca2d56a6 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -669,6 +669,9 @@ "label.deploymentplanner": "Deployment planner", "label.desc.db.stats": "Database Statistics", "label.desc.importexportinstancewizard": "Import and export Instances to/from an existing VMware cluster.", +"label.desc.import.ext.kvm.wizard": "Import libvirt domain from KVM Host", +"label.desc.import.local.kvm.wizard": "Import QCOW image from Local Storage", +"label.desc.import.shared.kvm.wizard": "Import QCOW image from Shared Storage", "label.desc.ingesttinstancewizard": "Ingest instances from an external KVM host", "label.desc.usage.stats": "Usage Server Statistics", "label.description": "Description", @@ -859,6 +862,7 @@ "label.expunged": "Expunged", "label.expunging": "Expunging", "label.export.rules": "Export Rules", +"label.ext.hostname.tooltip": "External Host Name or IP Address", "label.external.managed": "ExternalManaged", "label.external.link": "External link", "label.externalid": "External Id", @@ -970,6 +974,7 @@ "label.hostcontrolstate": "Control Plane Status", "label.hostid": "Host", "label.hostname": "Host", +"label.hostname.tooltip": "Destination Host. Volume should be located in local storage of this Host", "label.hostnamelabel": "Host name", "label.hosts": "Hosts", "label.hosttags": "Host tags", @@ -1480,6 +1485,7 @@ "label.password": "Password", "label.password.default": "Default Password", "label.password.reset.confirm": "Password has been reset to ", +"label.password.tooltip": "The password for the Host", "label.passwordenabled": "Password enabled", "label.path": "Path", "label.patp": "Palo Alto threat profile", @@ -1959,6 +1965,7 @@ "label.storagemotionenabled": "Storage motion enabled", "label.storagepolicy": "Storage policy", "label.storagepool": "Storage pool", +"label.storagepool.tooltip": "Destination Storage Pool. Volume should be located in this Storage Pool", "label.storagetags": "Storage tags", "label.storagetype": "Storage type", "label.strict": "Strict", @@ -2049,6 +2056,7 @@ "label.timeout.in.second ": " Timeout (seconds)", "label.timezone": "Timezone", "label.tmppath": "Temp Path", +"label.tmppath.tooltip": "Temporary Path to store disk images of External Vm before copying to destination storage pool. Default is /tmp", "label.to": "to", "label.token": "Token", "label.token.for.dashboard.login": "Token for dashboard login can be retrieved using following command", @@ -2152,6 +2160,7 @@ "label.userdata": "Userdata", "label.userdatal2": "User data", "label.username": "Username", +"label.username.tooltip": "The Username for the Host", "label.users": "Users", "label.usersource": "User type", "label.using.cli": "Using CLI", @@ -2639,6 +2648,9 @@ "message.desc.host": "Each cluster must contain at least one host (computer) for guest Instances to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.

Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.", "message.desc.importexportinstancewizard": "This feature only applies Cloudstack VMware clusters. By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. The Instance is left running and not physically moved. Unmanaging Instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", "message.desc.importingestinstancewizard": "This feature only applies to libvirt based KVM instances. Only Stopped instances can be ingested", +"message.desc.import.ext.kvm.wizard": "Import libvirt domain from External KVM Host not managed by CloudStack", +"message.desc.import.local.kvm.wizard": "Import QCOW image from Local Storage of selected KVM Host", +"message.desc.import.shared.kvm.wizard": "Import QCOW image from selected Primary Storage Pool", "message.desc.primary.storage": "Each cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the Instances running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.", "message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this Instance.", "message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.

Provide the IP address and exported path.", diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 8eb8b8ea94f4..f8c68d9aaacd 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -45,7 +45,7 @@ />
- + + ref="hostname"> + @@ -102,8 +106,12 @@ + ref="username"> + @@ -112,8 +120,12 @@ + ref="password"> + @@ -122,8 +134,12 @@ + ref="tmppath"> + @@ -136,7 +152,7 @@ - + @@ -211,8 +227,12 @@ + ref="hostid"> + + ref="poolid"> + -
Date: Thu, 9 Nov 2023 15:40:08 +0530 Subject: [PATCH 35/65] update kvm default template name --- .../java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index d883c0345299..d7caada522b5 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -161,7 +161,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; - public static final String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template.iso"; + public static final String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template"; private static final Logger LOGGER = Logger.getLogger(UnmanagedVMsManagerImpl.class); @Inject From 821a9724522b7558df2f6eb73c79975e8e1729dd Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 9 Nov 2023 11:21:30 -0300 Subject: [PATCH 36/65] Do not filter network by keys on KVM import unmanaged instances --- ui/src/views/tools/ImportUnmanagedInstance.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 14d413481a73..3abe2aaebfc1 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -270,7 +270,7 @@ :selectionEnabled="false" :filterUnimplementedNetworks="true" :hypervisor="this.cluster.hypervisortype" - filterMatchKey="broadcasturi" + :filterMatchKey="isKVMUnmanage ? undefined : 'broadcasturi'" @select-multi-network="updateMultiNetworkOffering" />
@@ -541,6 +541,9 @@ export default { } return false }, + isKVMUnmanage () { + return this.hypervisor && this.hypervisor === 'kvm' && this.importsource === 'unmanaged' + }, domainSelectOptions () { var domains = this.options.domains.map((domain) => { return { From e24f77ae45f8f530c5b9bf2a4494ba2bd5511020 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Fri, 10 Nov 2023 03:30:54 +0530 Subject: [PATCH 37/65] Do not allow import if volume is in use --- .../java/com/cloud/storage/dao/VolumeDao.java | 2 ++ .../com/cloud/storage/dao/VolumeDaoImpl.java | 14 ++++++++ .../vm/UnmanagedVMsManagerImpl.java | 34 ++++++++++++++----- .../views/tools/ImportUnmanagedInstance.vue | 2 +- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index 79899b7119e7..be6588e3189f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -152,5 +152,7 @@ public interface VolumeDao extends GenericDao, StateDao listByPoolIdAndPaths(long id, List pathList); + VolumeVO findByPoolIdAndPath(long id, String path); + List listByIds(List ids); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index 056b7206d726..bf5566224635 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -71,6 +71,7 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol protected GenericSearchBuilder primaryStorageSearch; protected GenericSearchBuilder primaryStorageSearch2; protected GenericSearchBuilder secondaryStorageSearch; + private final SearchBuilder poolAndPathSearch; @Inject ResourceTagDao _tagsDao; @@ -487,6 +488,11 @@ public VolumeDaoImpl() { volumeIdSearch.and("idIN", volumeIdSearch.entity().getId(), Op.IN); volumeIdSearch.done(); + poolAndPathSearch = createSearchBuilder(); + poolAndPathSearch.and("poolId", poolAndPathSearch.entity().getPoolId(), Op.EQ); + poolAndPathSearch.and("path", poolAndPathSearch.entity().getPath(), Op.EQ); + poolAndPathSearch.done(); + } @Override @@ -802,6 +808,14 @@ public List listByPoolIdAndPaths(long id, List pathList) { return listBy(sc); } + @Override + public VolumeVO findByPoolIdAndPath(long id, String path) { + SearchCriteria sc = poolAndPathSearch.create(); + sc.setParameters("poolId", id); + sc.setParameters("path", path); + return findOneBy(sc); + } + @Override public List listByIds(List ids) { if (CollectionUtils.isEmpty(ids)) { diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index d7caada522b5..b346ed2d5cf4 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -122,6 +122,7 @@ import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ResponseGenerator; @@ -1489,7 +1490,7 @@ private boolean existsVMToUnmanage(String instanceName, Long hostId) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_IMPORT, eventDescription = "importing VM", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_IMPORT, eventDescription = "importing VM") public UserVmResponse importVm(ImportVmCmd cmd) { final Account caller = CallContext.current().getCallingAccount(); if (caller.getType() != Account.Type.ADMIN) { @@ -1583,6 +1584,16 @@ public UserVmResponse importVm(ImportVmCmd cmd) { } } + if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) { + if (diskPath == null) { + throw new InvalidParameterValueException("Disk Path is required for Import from shared/local storage"); + } + + if (networkId == null) { + throw new InvalidParameterValueException("Network is required for Import from shared/local storage"); + } + } + if (ImportSource.SHARED == importSource) { if (poolId == null) { throw new InvalidParameterValueException("Storage Pool is required for Import from shared storage"); @@ -1591,6 +1602,10 @@ public UserVmResponse importVm(ImportVmCmd cmd) { if (primaryDataStoreDao.findById(poolId) == null) { throw new InvalidParameterValueException("Storage Pool not found"); } + + if (volumeDao.findByPoolIdAndPath(poolId, diskPath) != null) { + throw new InvalidParameterValueException("Disk image is already in use"); + } } if (ImportSource.LOCAL == importSource) { @@ -1601,15 +1616,12 @@ public UserVmResponse importVm(ImportVmCmd cmd) { if (hostDao.findById(hostId) == null) { throw new InvalidParameterValueException("Host not found"); } - } - - if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) { - if (diskPath == null) { - throw new InvalidParameterValueException("Disk Path is required for Import from shared/local storage"); - } - if (networkId == null) { - throw new InvalidParameterValueException("Network is required for Import from shared/local storage"); + List localPools = primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(hostId, null); + for(StoragePoolVO localPool : localPools) { + if (volumeDao.findByPoolIdAndPath(localPool.getId(), diskPath) != null) { + throw new InvalidParameterValueException("Disk image is already in use"); + } } } @@ -1638,6 +1650,9 @@ public UserVmResponse importVm(ImportVmCmd cmd) { if (userVm == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import Vm with name: %s ", instanceName)); } + + CallContext.current().setEventResourceId(userVm.getId()); + CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine); return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0); } @@ -1766,6 +1781,7 @@ private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource, final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, final Long networkId, final Long hostId, final Long poolId, final String diskPath, final Map details) throws InsufficientCapacityException, ResourceAllocationException { + UserVm userVm = null; Map allDetails = new HashMap<>(details); diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 3abe2aaebfc1..3d6fd84e72e2 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -940,7 +940,7 @@ export default { hostid: this.host.id, storageid: this.pool.id, diskpath: this.diskpath, - tmppath: this.tmppath + temppath: this.tmppath } var importapi = 'importUnmanagedInstance' if (this.isExternalImport || this.isDiskImport) { From 93ca130d928aba8ee6e319e4c3f53e833b679dcf Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Fri, 10 Nov 2023 03:35:30 +0530 Subject: [PATCH 38/65] skip filtering networks for external import --- ui/src/views/tools/ImportUnmanagedInstance.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 3d6fd84e72e2..120b2dce9d0f 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -542,7 +542,7 @@ export default { return false }, isKVMUnmanage () { - return this.hypervisor && this.hypervisor === 'kvm' && this.importsource === 'unmanaged' + return this.hypervisor && this.hypervisor === 'kvm' && (this.importsource === 'unmanaged' || this.importsource === 'external') }, domainSelectOptions () { var domains = this.options.domains.map((domain) => { From e8f169729ac47916d501ddcd52b465662574b571 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Fri, 10 Nov 2023 08:38:57 +0530 Subject: [PATCH 39/65] fix external import --- ui/src/views/tools/ImportUnmanagedInstance.vue | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 120b2dce9d0f..3de1aefe6f14 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -1086,9 +1086,6 @@ export default { nicIpIndex++ } } - if (this.isExternalImport) { - return - } this.updateLoading(true) const name = params.name return new Promise((resolve, reject) => { From 3ac39a7ab74b3d4df30470d4bf6cedd130b8e225 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Fri, 10 Nov 2023 11:14:19 -0300 Subject: [PATCH 40/65] Fix UI banner messages for unmanaging VMs --- ui/public/locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index ba63ca2d56a6..43014889c705 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -668,7 +668,7 @@ "label.deployasis": "Read Instance settings from OVA", "label.deploymentplanner": "Deployment planner", "label.desc.db.stats": "Database Statistics", -"label.desc.importexportinstancewizard": "Import and export Instances to/from an existing VMware cluster.", +"label.desc.importexportinstancewizard": "Import and export Instances to/from an existing VMware or KVM cluster.", "label.desc.import.ext.kvm.wizard": "Import libvirt domain from KVM Host", "label.desc.import.local.kvm.wizard": "Import QCOW image from Local Storage", "label.desc.import.shared.kvm.wizard": "Import QCOW image from Shared Storage", @@ -2646,7 +2646,7 @@ "message.desc.create.ssh.key.pair": "Please fill in the following data to create or register a ssh key pair.

(1) If public key is set, CloudStack will register the public key. You can use it through your private key.

(2) If public key is not set, CloudStack will create a new SSH key pair. In this case, please copy and save the private key. CloudStack will not keep it.
", "message.desc.created.ssh.key.pair": "Created a SSH key pair.", "message.desc.host": "Each cluster must contain at least one host (computer) for guest Instances to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.

Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.", -"message.desc.importexportinstancewizard": "This feature only applies Cloudstack VMware clusters. By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. The Instance is left running and not physically moved. Unmanaging Instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", +"message.desc.importexportinstancewizard": "By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. The Instance is left running and not physically moved. Unmanaging Instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", "message.desc.importingestinstancewizard": "This feature only applies to libvirt based KVM instances. Only Stopped instances can be ingested", "message.desc.import.ext.kvm.wizard": "Import libvirt domain from External KVM Host not managed by CloudStack", "message.desc.import.local.kvm.wizard": "Import QCOW image from Local Storage of selected KVM Host", From b82fe639ea6e53718a6e1bc002d9919f3c715c6d Mon Sep 17 00:00:00 2001 From: nvazquez Date: Fri, 10 Nov 2023 13:26:51 -0300 Subject: [PATCH 41/65] Fix KVM import unmanaged VM on local storage --- .../agent/api/GetUnmanagedInstancesAnswer.java | 4 ++++ .../kvm/resource/LibvirtComputingResource.java | 15 +++++++++++---- ...ibvirtGetUnmanagedInstancesCommandWrapper.java | 12 +++++++----- .../cloudstack/vm/UnmanagedVMsManagerImpl.java | 2 +- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java b/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java index 3c6118d426e6..771d472be2ae 100644 --- a/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java @@ -30,6 +30,10 @@ public class GetUnmanagedInstancesAnswer extends Answer { GetUnmanagedInstancesAnswer() { } + public GetUnmanagedInstancesAnswer(GetUnmanagedInstancesCommand cmd, String details) { + super(cmd, false, details); + } + public GetUnmanagedInstancesAnswer(GetUnmanagedInstancesCommand cmd, String details, HashMap unmanagedInstances) { super(cmd, true, details); this.instanceName = cmd.getInstanceName(); 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 6e102bba0458..4586628965c7 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 @@ -3703,15 +3703,22 @@ private String getIqn() { * @return Pair with IP of host and path */ public Pair getSourceHostPath(String diskPath) { - String sourceHostIp = null, sourcePath = null; + String sourceHostIp = null; + String sourcePath = null; try { String mountResult = Script.runSimpleBashScript("mount | grep \"" + diskPath + "\""); s_logger.debug("Got mount result for " + diskPath + "\n\n" + mountResult); if (StringUtils.isNotEmpty(mountResult)) { String[] res = mountResult.strip().split(" "); - res = res[0].split(":"); - sourceHostIp = res[0].strip(); - sourcePath = res[1].strip(); + if (res[0].contains(":")) { + res = res[0].split(":"); + sourceHostIp = res[0].strip(); + sourcePath = res[1].strip(); + } else { + // Assume local storage + sourceHostIp = getPrivateIp(); + sourcePath = diskPath; + } } if (StringUtils.isNotEmpty(sourceHostIp) && StringUtils.isNotEmpty(sourcePath)) { return new Pair<>(sourceHostIp, sourcePath); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java index 78877ee0be0d..8e8820589949 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java @@ -64,11 +64,12 @@ public GetUnmanagedInstancesAnswer execute(GetUnmanagedInstancesCommand command, } } } catch (Exception e) { - LOGGER.error("GetUnmanagedInstancesCommand failed due to " + e.getMessage()); - throw new CloudRuntimeException("GetUnmanagedInstancesCommand failed due to " + e.getMessage()); + String err = String.format("Error listing unmanaged instances: %s", e.getMessage()); + LOGGER.error(err, e); + return new GetUnmanagedInstancesAnswer(command, err); } - return new GetUnmanagedInstancesAnswer(command, "True", unmanagedInstances); + return new GetUnmanagedInstancesAnswer(command, "OK", unmanagedInstances); } private List getDomains(GetUnmanagedInstancesCommand command, @@ -79,8 +80,9 @@ private List getDomains(GetUnmanagedInstancesCommand command, if (StringUtils.isNotBlank(vmNameCmd)) { final Domain domain = libvirtComputingResource.getDomain(conn, vmNameCmd); if (domain == null) { - LOGGER.error("GetUnmanagedInstancesCommand: vm not found " + vmNameCmd); - throw new CloudRuntimeException("GetUnmanagedInstancesCommand: vm not found " + vmNameCmd); + String msg = String.format("VM %s not found", vmNameCmd); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); } checkIfVmExists(vmNameCmd,domain); diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index b346ed2d5cf4..7ed009328a52 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -1170,7 +1170,7 @@ private HashMap getUnmanagedInstancesForHost(HostVO command.setInstanceName(instanceName); command.setManagedInstancesNames(managedVms); Answer answer = agentManager.easySend(host.getId(), command); - if (!(answer instanceof GetUnmanagedInstancesAnswer)) { + if (!(answer instanceof GetUnmanagedInstancesAnswer) || !answer.getResult()) { return unmanagedInstances; } GetUnmanagedInstancesAnswer unmanagedInstancesAnswer = (GetUnmanagedInstancesAnswer) answer; From 97982aabc9c22ddd063ec6b6f2b59c2e9f868565 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Sat, 11 Nov 2023 04:27:05 +0530 Subject: [PATCH 42/65] Ensure tmp path ends with a / --- .../org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 7ed009328a52..8ec159bd1310 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -741,6 +741,11 @@ private Pair importExternalDisk(UnmanagedInstanceTO.Di copyRemoteVolumeCommand.setStorageFilerTO(storageTO); if(tmpPath == null) { tmpPath = "/tmp/"; + } else { + // Add / if path doesn't end with / + if(tmpPath.charAt(tmpPath.length() - 1) != '/') { + tmpPath += "/"; + } } copyRemoteVolumeCommand.setTempPath(tmpPath); Answer answer = agentManager.easySend(dest.getHost().getId(), copyRemoteVolumeCommand); From c0af9df0e47c38795df1bcf181251a3006e3762a Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Sat, 11 Nov 2023 06:50:30 +0530 Subject: [PATCH 43/65] check for empty temp path --- .../java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 2 +- ui/public/locales/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 8ec159bd1310..44ca9cf50b81 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -739,7 +739,7 @@ private Pair importExternalDisk(UnmanagedInstanceTO.Di copyRemoteVolumeCommand.setSrcFile(path); StorageFilerTO storageTO = new StorageFilerTO(storagePool); copyRemoteVolumeCommand.setStorageFilerTO(storageTO); - if(tmpPath == null) { + if(tmpPath == null || tmpPath.length() < 1) { tmpPath = "/tmp/"; } else { // Add / if path doesn't end with / diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 43014889c705..6403729d1a92 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2056,7 +2056,7 @@ "label.timeout.in.second ": " Timeout (seconds)", "label.timezone": "Timezone", "label.tmppath": "Temp Path", -"label.tmppath.tooltip": "Temporary Path to store disk images of External Vm before copying to destination storage pool. Default is /tmp", +"label.tmppath.tooltip": "Temporary Path to store disk images on External Vm before copying to destination storage pool. Default is /tmp", "label.to": "to", "label.token": "Token", "label.token.for.dashboard.login": "Token for dashboard login can be retrieved using following command", From e030e598398beefe31cb9d94727c27441656cd53 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 14 Nov 2023 13:34:36 +0530 Subject: [PATCH 44/65] Fixed indentation and a logger initialisation --- .../main/java/com/cloud/vm/VmDetailConstants.java | 1 - .../admin/storage/ListStoragePoolsCmd.java | 1 - .../apache/cloudstack/vm/UnmanagedInstanceTO.java | 3 ++- .../kvm/resource/LibvirtDomainXMLParser.java | 15 +++++++-------- ...ibvirtGetUnmanagedInstancesCommandWrapper.java | 2 +- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 2 +- .../vm/UnmanagedVMsManagerImplTest.java | 1 - 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index d6b007c7640c..2ec83f5b5a43 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -39,7 +39,6 @@ public interface VmDetailConstants { // KVM specific (internal) String KVM_VNC_PORT = "kvm.vnc.port"; String KVM_VNC_ADDRESS = "kvm.vnc.address"; - String KVM_VNC_PASSWORD = "kvm.vnc.password"; // KVM specific, custom virtual GPU hardware diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java index aed91aa79d7b..6923353b3bf5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java @@ -58,7 +58,6 @@ public class ListStoragePoolsCmd extends BaseListCmd { @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, description = "the Pod ID for the storage pool") private Long podId; - @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the Zone ID for the storage pool") private Long zoneId; diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java index 1ade095667eb..f759414eb897 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java @@ -48,6 +48,7 @@ public enum PowerState { private List disks; private List nics; + private String vncPassword; public String getName() { @@ -172,6 +173,7 @@ public static class Disk { private String datastorePath; private int datastorePort; + private String datastoreType; public String getDiskId() { @@ -278,7 +280,6 @@ public void setDatastoreType(String datastoreType) { this.datastoreType = datastoreType; } - public void setDatastorePort(int datastorePort) { this.datastorePort = datastorePort; } 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 094c5c4a923e..f165796adef2 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 @@ -60,9 +60,7 @@ public class LibvirtDomainXMLParser { private String vncPasswd; private String desc; private LibvirtVMDef.CpuTuneDef cpuTuneDef; - private LibvirtVMDef.CpuModeDef cpuModeDef; - private String name; public boolean parseDomainXML(String domXML) { @@ -282,11 +280,11 @@ public boolean parseDomainXML(String domXML) { String name = getAttrValue("target", "name", channel); String state = getAttrValue("target", "state", channel); - if ( ChannelDef.ChannelType.valueOf(type.toUpperCase()).equals(ChannelDef.ChannelType.SPICEVMC)) { + if (ChannelDef.ChannelType.valueOf(type.toUpperCase()).equals(ChannelDef.ChannelType.SPICEVMC)) { continue; } - if(path == null) { + if (path == null) { path = ""; } @@ -337,19 +335,19 @@ public boolean parseDomainXML(String domXML) { s_logger.debug(String.format("Bytes and period in the rng section should not be null, please check the VM %s", name)); } - if(bytes == null) { + if (bytes == null) { bytes = "0"; } - if(period == null) { + if (period == null) { period = "0"; } - if(bytes == null) { + if (bytes == null) { bytes = "0"; } - if(period == null) { + if (period == null) { period = "0"; } @@ -453,6 +451,7 @@ public List getInterfaces() { public String getVncPasswd() { return vncPasswd; } + public MemBalloonDef getMemBalloon() { return memBalloonDef; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java index 8e8820589949..a2d84063d741 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java @@ -44,7 +44,7 @@ @ResourceWrapper(handles=GetUnmanagedInstancesCommand.class) public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWrapper { - private static final Logger LOGGER = Logger.getLogger(LibvirtPrepareUnmanageVMInstanceCommandWrapper.class); + private static final Logger LOGGER = Logger.getLogger(LibvirtGetUnmanagedInstancesCommandWrapper.class); @Override public GetUnmanagedInstancesAnswer execute(GetUnmanagedInstancesCommand command, LibvirtComputingResource libvirtComputingResource) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 2e7cb1713f55..8346cefc6f09 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4488,7 +4488,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin if (isImport) { vm.setDataCenterId(zone.getId()); - if(host != null) { + if (host != null) { vm.setHostId(host.getId()); } if (lastHost != null) { diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index a9b6f03ecdd6..5a9d7b40d4ad 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -17,7 +17,6 @@ package org.apache.cloudstack.vm; - import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.GetUnmanagedInstancesAnswer; From 21e1c92c5a8b770a023fc2af309704c18f45fdcc Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 14 Nov 2023 16:24:38 +0530 Subject: [PATCH 45/65] Revert the changes of VM create event --- server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 8346cefc6f09..e95765a3afa5 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4876,7 +4876,7 @@ public String validateUserData(String userData, HTTPMethod httpmethod) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", async = true) public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException { long vmId = cmd.getEntityId(); if (!cmd.getStartVm()) { From 0ddd17e7b8f7e826bbd38029bd82f31c5c3eeebd Mon Sep 17 00:00:00 2001 From: nvazquez Date: Sat, 18 Nov 2023 18:46:53 -0300 Subject: [PATCH 46/65] Fix import unmanaged instance events --- .../command/admin/vm/ImportUnmanagedInstanceCmd.java | 3 ++- .../apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java index 4fa68a0d2a07..68645fb757f0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java @@ -279,7 +279,8 @@ public String getEventType() { @Override public String getEventDescription() { - return "Importing unmanaged VM"; + String vmName = this.name; + return String.format("Importing unmanaged VM: %s", vmName); } public boolean isForced() { diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 44ca9cf50b81..a6632de68163 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -41,7 +41,9 @@ import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; +import com.cloud.event.EventVO; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientCapacityException; @@ -1317,6 +1319,10 @@ public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) { List additionalNameFilters = getAdditionalNameFilters(cluster); List managedVms = new ArrayList<>(additionalNameFilters); managedVms.addAll(getHostsManagedVms(hosts)); + + ActionEventUtils.onStartedActionEvent(userId, owner.getId(), EventTypes.EVENT_VM_IMPORT, + cmd.getEventDescription(), null, null, true, 0); + for (HostVO host : hosts) { HashMap unmanagedInstances = getUnmanagedInstancesForHost(host, cmd.getName(), managedVms); if (MapUtils.isEmpty(unmanagedInstances)) { @@ -1371,8 +1377,12 @@ public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) { } } if (userVm == null) { + ActionEventUtils.onCompletedActionEvent(userId, owner.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_IMPORT, + cmd.getEventDescription(), null, null, 0); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to find unmanaged vm with name: %s in cluster: %s", instanceName, cluster.getUuid())); } + ActionEventUtils.onCompletedActionEvent(userId, owner.getId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_IMPORT, + cmd.getEventDescription(), userVm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0); return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0); } From 6127b30e4b116ba519a404930648ed6041230fe9 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 20 Nov 2023 07:41:13 -0300 Subject: [PATCH 47/65] Fix unit tests --- .../apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 5a9d7b40d4ad..5d3ff1b35c99 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -27,6 +27,7 @@ import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.event.ActionEventUtils; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; @@ -363,7 +364,8 @@ private void testImportUnmanagedInstanceTest(Hypervisor.HypervisorType hyperviso ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); - try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class); + MockedStatic ignored2 = Mockito.mockStatic(ActionEventUtils.class)) { unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd); } } @@ -393,7 +395,9 @@ private void testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.Hyperviso ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("SomeInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); - unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd); + try (MockedStatic ignored2 = Mockito.mockStatic(ActionEventUtils.class)) { + unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd); + } } @Test(expected = InvalidParameterValueException.class) From c1109f3995cfffb92cc4af199b7f1cace17463c0 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Wed, 22 Nov 2023 00:57:21 +0530 Subject: [PATCH 48/65] Add unit tests for KVM import --- .../vm/UnmanagedVMsManagerImplTest.java | 168 +++++++++++++++--- 1 file changed, 148 insertions(+), 20 deletions(-) diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 5d3ff1b35c99..60829718c791 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -19,6 +19,12 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckVolumeAnswer; +import com.cloud.agent.api.CheckVolumeCommand; +import com.cloud.agent.api.CopyRemoteVolumeAnswer; +import com.cloud.agent.api.CopyRemoteVolumeCommand; +import com.cloud.agent.api.GetRemoteVmsAnswer; +import com.cloud.agent.api.GetRemoteVmsCommand; import com.cloud.agent.api.GetUnmanagedInstancesAnswer; import com.cloud.agent.api.GetUnmanagedInstancesCommand; import com.cloud.configuration.Resource; @@ -27,11 +33,18 @@ import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.event.ActionEventUtils; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @@ -55,13 +68,16 @@ import com.cloud.network.NetworkModel; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.resource.ResourceManager; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; @@ -79,6 +95,8 @@ import com.cloud.user.dao.UserDao; import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.DiskProfile; import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; import com.cloud.vm.UserVmManager; @@ -93,7 +111,10 @@ import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd; +import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd; import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd; +import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd; +import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -102,6 +123,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -182,6 +204,10 @@ public class UnmanagedVMsManagerImplTest { private VMInstanceVO virtualMachine; @Mock private NicVO nicVO; + @Mock + EntityManager entityMgr; + @Mock + DeploymentPlanningManager deploymentPlanningManager; private static final long virtualMachineId = 1L; @@ -226,43 +252,47 @@ public void setUp() throws Exception { instance.setPowerState(UnmanagedInstanceTO.PowerState.PowerOn); clusterVO = new ClusterVO(1L, 1L, "Cluster"); - when(clusterDao.findById(Mockito.anyLong())).thenReturn(clusterVO); - when(configurationDao.getValue(Mockito.anyString())).thenReturn(null); - doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(Account.class), Mockito.any(Resource.ResourceType.class), Mockito.anyLong()); + when(clusterDao.findById(anyLong())).thenReturn(clusterVO); + when(configurationDao.getValue(anyString())).thenReturn(null); + doNothing().when(resourceLimitService).checkResourceLimit(any(Account.class), any(Resource.ResourceType.class), anyLong()); List hosts = new ArrayList<>(); HostVO hostVO = Mockito.mock(HostVO.class); when(hostVO.isInMaintenanceStates()).thenReturn(false); hosts.add(hostVO); - when(hostVO.checkHostServiceOfferingTags(Mockito.any())).thenReturn(true); - when(resourceManager.listHostsInClusterByStatus(Mockito.anyLong(), Mockito.any(Status.class))).thenReturn(hosts); + when(hostVO.checkHostServiceOfferingTags(any())).thenReturn(true); + when(resourceManager.listHostsInClusterByStatus(anyLong(), any(Status.class))).thenReturn(hosts); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(any(Hypervisor.HypervisorType.class), Mockito.anyLong())).thenReturn(hosts); List templates = new ArrayList<>(); when(templatePoolDao.listAll()).thenReturn(templates); List volumes = new ArrayList<>(); - when(volumeDao.findIncludingRemovedByZone(Mockito.anyLong())).thenReturn(volumes); + when(volumeDao.findIncludingRemovedByZone(anyLong())).thenReturn(volumes); GetUnmanagedInstancesCommand cmd = Mockito.mock(GetUnmanagedInstancesCommand.class); HashMap map = new HashMap<>(); map.put(instance.getName(), instance); Answer answer = new GetUnmanagedInstancesAnswer(cmd, "", map); - when(agentManager.easySend(Mockito.anyLong(), Mockito.any(GetUnmanagedInstancesCommand.class))).thenReturn(answer); + when(agentManager.easySend(anyLong(), any(GetUnmanagedInstancesCommand.class))).thenReturn(answer); + GetRemoteVmsCommand remoteVmListcmd = Mockito.mock(GetRemoteVmsCommand.class); + Answer remoteVmListAnswer = new GetRemoteVmsAnswer(remoteVmListcmd, "", map); + when(agentManager.easySend(Mockito.anyLong(), any(GetRemoteVmsCommand.class))).thenReturn(remoteVmListAnswer); DataCenterVO zone = Mockito.mock(DataCenterVO.class); when(zone.getId()).thenReturn(1L); - when(dataCenterDao.findById(Mockito.anyLong())).thenReturn(zone); - when(accountService.getActiveAccountById(Mockito.anyLong())).thenReturn(Mockito.mock(Account.class)); + when(dataCenterDao.findById(anyLong())).thenReturn(zone); + when(accountService.getActiveAccountById(anyLong())).thenReturn(Mockito.mock(Account.class)); List users = new ArrayList<>(); users.add(Mockito.mock(UserVO.class)); - when(userDao.listByAccount(Mockito.anyLong())).thenReturn(users); + when(userDao.listByAccount(anyLong())).thenReturn(users); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); when(template.getName()).thenReturn("Template"); - when(templateDao.findById(Mockito.anyLong())).thenReturn(template); + when(templateDao.findById(anyLong())).thenReturn(template); ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); when(serviceOffering.getId()).thenReturn(1L); when(serviceOffering.isDynamic()).thenReturn(false); when(serviceOffering.getCpu()).thenReturn(instance.getCpuCores()); when(serviceOffering.getRamSize()).thenReturn(instance.getMemory()); when(serviceOffering.getSpeed()).thenReturn(instance.getCpuSpeed()); - when(serviceOfferingDao.findById(Mockito.anyLong())).thenReturn(serviceOffering); + when(serviceOfferingDao.findById(anyLong())).thenReturn(serviceOffering); DiskOfferingVO diskOfferingVO = Mockito.mock(DiskOfferingVO.class); - when(diskOfferingDao.findById(Mockito.anyLong())).thenReturn(diskOfferingVO); + when(diskOfferingDao.findById(anyLong())).thenReturn(diskOfferingVO); userVm = Mockito.mock(UserVmVO.class); when(userVm.getAccountId()).thenReturn(1L); when(userVm.getDataCenterId()).thenReturn(1L); @@ -279,7 +309,7 @@ public void setUp() throws Exception { when(poolVO.getClusterId()).thenReturn(clusterVO.getId()); List pools = new ArrayList<>(); pools.add(poolVO); - when(primaryDataStoreDao.listPoolByHostPath(Mockito.anyString(), Mockito.anyString())).thenReturn(pools); + when(primaryDataStoreDao.listPoolByHostPath(anyString(), anyString())).thenReturn(pools); when(userVmManager.importVM(nullable(DataCenter.class), nullable(Host.class), nullable(VirtualMachineTemplate.class), nullable(String.class), nullable(String.class), nullable(Account.class), nullable(String.class), nullable(Account.class), nullable(Boolean.class), nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(ServiceOffering.class), nullable(String.class), @@ -287,21 +317,21 @@ public void setUp() throws Exception { NetworkVO networkVO = Mockito.mock(NetworkVO.class); when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2); when(networkVO.getDataCenterId()).thenReturn(1L); - when(networkDao.findById(Mockito.anyLong())).thenReturn(networkVO); + when(networkDao.findById(anyLong())).thenReturn(networkVO); List networks = new ArrayList<>(); networks.add(networkVO); - when(networkDao.listByZone(Mockito.anyLong())).thenReturn(networks); - doNothing().when(networkModel).checkNetworkPermissions(Mockito.any(Account.class), Mockito.any(Network.class)); + when(networkDao.listByZone(anyLong())).thenReturn(networks); + doNothing().when(networkModel).checkNetworkPermissions(any(Account.class), any(Network.class)); NicProfile profile = Mockito.mock(NicProfile.class); Integer deviceId = 100; Pair pair = new Pair(profile, deviceId); when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), Mockito.anyBoolean())).thenReturn(pair); - when(volumeDao.findByInstance(Mockito.anyLong())).thenReturn(volumes); + when(volumeDao.findByInstance(anyLong())).thenReturn(volumes); List userVmResponses = new ArrayList<>(); UserVmResponse userVmResponse = new UserVmResponse(); userVmResponse.setInstanceName(instance.getName()); userVmResponses.add(userVmResponse); - when(responseGenerator.createUserVmResponse(Mockito.any(ResponseObject.ResponseView.class), Mockito.anyString(), Mockito.any(UserVm.class))).thenReturn(userVmResponses); + when(responseGenerator.createUserVmResponse(any(ResponseObject.ResponseView.class), anyString(), any(UserVm.class))).thenReturn(userVmResponses); when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Running); } @@ -331,7 +361,7 @@ public void listUnmanagedInstancesInvalidHypervisorTest() { ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class); ClusterVO cluster = new ClusterVO(1, 1, "Cluster"); cluster.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString()); - when(clusterDao.findById(Mockito.anyLong())).thenReturn(cluster); + when(clusterDao.findById(anyLong())).thenReturn(cluster); unmanagedVMsManager.listUnmanagedInstances(cmd); } @@ -473,4 +503,102 @@ private void testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.Hypervisor UserVmVO userVmVO = mock(UserVmVO.class); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } + + @Test + public void testListRemoteInstancesTest() { + ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class); + when(cmd.getHypervisor()).thenReturn(Hypervisor.HypervisorType.KVM.toString()); + when(cmd.getUsername()).thenReturn("user"); + when(cmd.getPassword()).thenReturn("pass"); + ListResponse response = unmanagedVMsManager.listVmsForImport(cmd); + Assert.assertEquals(1, response.getCount().intValue()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testListRemoteInstancesTestNonKVM() { + ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class); + unmanagedVMsManager.listVmsForImport(cmd); + } + @Test + public void testImportFromExternalTest() throws InsufficientServerCapacityException { + String vmname = "TestInstance"; + ImportVmCmd cmd = Mockito.mock(ImportVmCmd.class); + when(cmd.getHypervisor()).thenReturn(Hypervisor.HypervisorType.KVM.toString()); + when(cmd.getName()).thenReturn(vmname); + when(cmd.getUsername()).thenReturn("user"); + when(cmd.getPassword()).thenReturn("pass"); + when(cmd.getImportSource()).thenReturn("external"); + when(cmd.getDomainId()).thenReturn(null); + VMTemplateVO template = Mockito.mock(VMTemplateVO.class); + when(templateDao.findByName(anyString())).thenReturn(template); + HostVO host = Mockito.mock(HostVO.class); + when(userVmDao.getNextInSequence(Long.class, "id")).thenReturn(1L); + DeployDestination mockDest = Mockito.mock(DeployDestination.class); + when(deploymentPlanningManager.planDeployment(any(), any(), any(), any())).thenReturn(mockDest); + DiskProfile diskProfile = Mockito.mock(DiskProfile.class); + when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) + .thenReturn(diskProfile); + Map storage = new HashMap<>(); + VolumeVO volume = Mockito.mock(VolumeVO.class); + StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class); + storage.put(volume, storagePool); + when(mockDest.getStorageForDisks()).thenReturn(storage); + when(mockDest.getHost()).thenReturn(host); + when(volumeDao.findById(anyLong())).thenReturn(volume); + CopyRemoteVolumeAnswer copyAnswer = Mockito.mock(CopyRemoteVolumeAnswer.class); + when(copyAnswer.getResult()).thenReturn(true); + when(agentManager.easySend(anyLong(), any(CopyRemoteVolumeCommand.class))).thenReturn(copyAnswer); + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + unmanagedVMsManager.importVm(cmd); + } + } + + @Test + public void testImportFromLocalDisk() throws InsufficientServerCapacityException { + testImportFromDisk("local"); + } + + @Test + public void testImportFromsharedStorage() throws InsufficientServerCapacityException { + testImportFromDisk("shared"); + } + + private void testImportFromDisk(String source) throws InsufficientServerCapacityException { + String vmname = "testVm"; + ImportVmCmd cmd = Mockito.mock(ImportVmCmd.class); + when(cmd.getHypervisor()).thenReturn(Hypervisor.HypervisorType.KVM.toString()); + when(cmd.getName()).thenReturn(vmname); + when(cmd.getImportSource()).thenReturn(source); + when(cmd.getDiskPath()).thenReturn("/var/lib/libvirt/images/test.qcow2"); + when(cmd.getDomainId()).thenReturn(null); + VMTemplateVO template = Mockito.mock(VMTemplateVO.class); + when(templateDao.findByName(anyString())).thenReturn(template); + HostVO host = Mockito.mock(HostVO.class); + when(hostDao.findById(anyLong())).thenReturn(host); + NetworkOffering netOffering = Mockito.mock(NetworkOffering.class); + when(entityMgr.findById(NetworkOffering.class, 0L)).thenReturn(netOffering); + when(userVmDao.getNextInSequence(Long.class, "id")).thenReturn(1L); + DeployDestination mockDest = Mockito.mock(DeployDestination.class); + when(deploymentPlanningManager.planDeployment(any(), any(), any(), any())).thenReturn(mockDest); + DiskProfile diskProfile = Mockito.mock(DiskProfile.class); + when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) + .thenReturn(diskProfile); + Map storage = new HashMap<>(); + VolumeVO volume = Mockito.mock(VolumeVO.class); + StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class); + storage.put(volume, storagePool); + when(mockDest.getStorageForDisks()).thenReturn(storage); + when(mockDest.getHost()).thenReturn(host); + when(volumeDao.findById(anyLong())).thenReturn(volume); + CheckVolumeAnswer answer = Mockito.mock(CheckVolumeAnswer.class); + when(answer.getResult()).thenReturn(true); + when(agentManager.easySend(anyLong(), any(CheckVolumeCommand.class))).thenReturn(answer); + List storagePools = new ArrayList<>(); + storagePools.add(storagePool); + when(primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(anyLong(), any())).thenReturn(storagePools); + when(primaryDataStoreDao.findById(anyLong())).thenReturn(storagePool); + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { + unmanagedVMsManager.importVm(cmd); + } + } } From f6ad2985d3de0b4f5f2fe3166fc00ec0828df0b5 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 4 Dec 2023 18:55:21 -0300 Subject: [PATCH 49/65] Display unmanage VM button for KVM and do not let use an offering that doesnt match on KVM manage --- ui/src/config/section/compute.js | 2 +- ui/src/views/compute/wizard/ComputeOfferingSelection.vue | 7 ++++++- ui/src/views/tools/ImportUnmanagedInstance.vue | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 16349668650f..b56c8eeead94 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -424,7 +424,7 @@ export default { label: 'label.action.unmanage.virtualmachine', message: 'message.action.unmanage.virtualmachine', dataView: true, - show: (record) => { return ['Running', 'Stopped'].includes(record.state) && record.hypervisor === 'VMware' } + show: (record) => { return ['Running', 'Stopped'].includes(record.state) && ['VMware', 'KVM'].includes(record.hypervisor) } }, { api: 'expungeVirtualMachine', diff --git a/ui/src/views/compute/wizard/ComputeOfferingSelection.vue b/ui/src/views/compute/wizard/ComputeOfferingSelection.vue index aa97f36fff86..fa44882d77c5 100644 --- a/ui/src/views/compute/wizard/ComputeOfferingSelection.vue +++ b/ui/src/views/compute/wizard/ComputeOfferingSelection.vue @@ -104,6 +104,11 @@ export default { minimumMemory: { type: Number, default: 0 + }, + exactMatch: { + type: Boolean, + required: false, + default: false } }, data () { @@ -168,7 +173,7 @@ export default { disabled = true } if (disabled === false && maxMemory && this.minimumMemory > 0 && - ((item.iscustomized === false && maxMemory < this.minimumMemory) || + ((item.iscustomized === false && ((maxMemory < this.minimumMemory) || this.exactMatch && maxMemory !== this.minimumMemory)) || (item.iscustomized === true && maxMemory < this.minimumMemory))) { disabled = true } diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 3de1aefe6f14..fa8c3cfbf4c1 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -204,6 +204,7 @@ :minimumCpunumber="isVmRunning ? resource.cpunumber : null" :minimumCpuspeed="isVmRunning ? resource.cpuspeed : null" :minimumMemory="isVmRunning ? resource.memory : null" + :exactMatch="isKVMUnmanage ? true : false" size="small" @select-compute-item="($event) => updateComputeOffering($event)" @handle-search-filter="($event) => fetchComputeOfferings($event)" /> From 6c7b6eb4f90df143228d14ef3d35ce256abafb38 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Mon, 4 Dec 2023 19:20:36 -0300 Subject: [PATCH 50/65] Update ui/public/locales/en.json Co-authored-by: Andrija Panic <45762285+andrijapanicsb@users.noreply.github.com> --- ui/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 0b9ab67c5a3c..e11a3f58785c 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2678,7 +2678,7 @@ "message.desc.create.ssh.key.pair": "Please fill in the following data to create or register a ssh key pair.

(1) If public key is set, CloudStack will register the public key. You can use it through your private key.

(2) If public key is not set, CloudStack will create a new SSH key pair. In this case, please copy and save the private key. CloudStack will not keep it.
", "message.desc.created.ssh.key.pair": "Created a SSH key pair.", "message.desc.host": "Each cluster must contain at least one host (computer) for guest Instances to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.

Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.", -"message.desc.importexportinstancewizard": "By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. The Instance is left running and not physically moved. Unmanaging Instances, removes CloudStack ability to manage them (but they are left running and not destroyed).", +"message.desc.importexportinstancewizard": "By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. Unmanaging an Instance removes CloudStack's ability to manage it. In both cases, the Instance is left running and no changes are done to the VM on the hypervisor.", "message.desc.importingestinstancewizard": "This feature only applies to libvirt based KVM instances. Only Stopped instances can be ingested", "message.desc.import.ext.kvm.wizard": "Import libvirt domain from External KVM Host not managed by CloudStack", "message.desc.import.local.kvm.wizard": "Import QCOW image from Local Storage of selected KVM Host", From f2bebff5edbbcaa11e1d63473848a26c15d60334 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Tue, 5 Dec 2023 10:35:37 +0530 Subject: [PATCH 51/65] Support zone wide pools. Add check for validating storage tags --- .../api/command/admin/vm/ImportVmCmd.java | 2 +- .../vm/UnmanagedVMsManagerImpl.java | 23 ++++++---- ui/public/locales/en.json | 3 +- ui/src/views/tools/ManageInstances.vue | 45 ++++++++++++++++--- 4 files changed, 56 insertions(+), 17 deletions(-) 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 87e164a919d2..24bb9e512a7a 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 @@ -345,7 +345,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Importing unmanaged VM"; + return "Importing VM"; } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index a6632de68163..2fbd6b708572 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -91,6 +91,7 @@ import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.GuestOSHypervisorDao; import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VolumeDao; @@ -241,6 +242,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { private PhysicalNetworkDao physicalNetworkDao; @Inject private IpAddressManager ipAddressManager; + @Inject + private StoragePoolHostDao storagePoolHostDao; protected Gson gson; @@ -1607,20 +1610,25 @@ public UserVmResponse importVm(ImportVmCmd cmd) { if (networkId == null) { throw new InvalidParameterValueException("Network is required for Import from shared/local storage"); } - } - if (ImportSource.SHARED == importSource) { if (poolId == null) { - throw new InvalidParameterValueException("Storage Pool is required for Import from shared storage"); + throw new InvalidParameterValueException("Storage Pool is required for Import from shared/local storage"); } - if (primaryDataStoreDao.findById(poolId) == null) { + StoragePool storagePool = primaryDataStoreDao.findById(poolId); + if (storagePool == null) { throw new InvalidParameterValueException("Storage Pool not found"); } if (volumeDao.findByPoolIdAndPath(poolId, diskPath) != null) { throw new InvalidParameterValueException("Disk image is already in use"); } + + DiskOffering diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + + if (diskOffering != null && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) { + throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with selected storage pool: %s", serviceOffering.getUuid(), storagePool.getUuid())); + } } if (ImportSource.LOCAL == importSource) { @@ -1632,11 +1640,8 @@ public UserVmResponse importVm(ImportVmCmd cmd) { throw new InvalidParameterValueException("Host not found"); } - List localPools = primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(hostId, null); - for(StoragePoolVO localPool : localPools) { - if (volumeDao.findByPoolIdAndPath(localPool.getId(), diskPath) != null) { - throw new InvalidParameterValueException("Disk image is already in use"); - } + if(storagePoolHostDao.findByPoolHost(poolId, hostId) == null) { + throw new InvalidParameterValueException("Specified Local Storage Pool not found on Host"); } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index e11a3f58785c..0f3a204cfcc9 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -715,7 +715,7 @@ "label.disk": "Disk", "label.disk.offerings": "Disk offerings", "label.disk.path": "Disk Path", -"label.disk.path.tooltip": "Disk Path relative to the selected Storage Pool", +"label.disk.tooltip": "Disk Image filename in the selected Storage Pool", "label.disk.selection": "Disk selection", "label.disk.size": "Disk size", "label.disk.usage.info": "Disk usage information", @@ -1799,6 +1799,7 @@ "label.scheduled.snapshots": "Scheduled Snapshots", "label.schedules": "Schedules", "label.scope": "Scope", +"label.scope.tooltip": "Primary Storage Pool Scope", "label.search": "Search", "label.secondary.isolated.vlan.type.isolated": "Isolated", "label.secondary.isolated.vlan.type.promiscuous": "Promiscuous", diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index f8c68d9aaacd..ebdeabb5296c 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -164,6 +164,22 @@ :rules="rules" layout="vertical" > + + + + {{ $t('label.clusterid') }} + {{ $t('label.zoneid') }} + + - + + @change="onSelectRootDisk"> {{ opt.label || opt.id }} + + + 1) { + this.updateSelectedRootDisk() + } }, getMeta (obj, metaKeys) { var meta = [] @@ -922,6 +948,17 @@ export default { } ] }, + onSelectRootDisk (val) { + this.selectedRootDiskIndex = val + this.updateSelectedRootDisk() + }, + updateSelectedRootDisk () { + var rootDisk = this.resource.disk[this.selectedRootDiskIndex] + rootDisk.size = rootDisk.capacity / (1024 * 1024 * 1024) + rootDisk.name = `${rootDisk.label} (${rootDisk.size} GB)` + rootDisk.meta = this.getMeta(rootDisk, { controller: 'controller', datastorename: 'datastore', position: 'position' }) + this.selectedRootDiskSources = [rootDisk] + }, handleSubmit (e) { e.preventDefault() if (this.loading) return From f5cee6f91d96e0c8c30623448592ab9611fdc55d Mon Sep 17 00:00:00 2001 From: nvazquez Date: Wed, 6 Dec 2023 08:15:19 -0300 Subject: [PATCH 54/65] Do not display datastore on KVM unmanage UI --- ui/src/views/compute/wizard/MultiDiskSelection.vue | 6 +++++- ui/src/views/tools/ImportUnmanagedInstance.vue | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/src/views/compute/wizard/MultiDiskSelection.vue b/ui/src/views/compute/wizard/MultiDiskSelection.vue index 5dd8466279f4..8344508ad33c 100644 --- a/ui/src/views/compute/wizard/MultiDiskSelection.vue +++ b/ui/src/views/compute/wizard/MultiDiskSelection.vue @@ -31,7 +31,7 @@ {{ record.displaytext || record.name }}
- {{ meta.key + ': ' + meta.value }} + {{ meta.key + ': ' + meta.value }}
@@ -104,6 +104,10 @@ export default { autoSelectLabel: { type: String, default: '' + }, + isKVMUnmanage: { + type: Boolean, + default: false } }, data () { diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 31618d5bbfee..60512be4b50a 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -257,7 +257,7 @@ {{ record.displaytext || record.name }}
- {{ meta.key + ': ' + meta.value }} + {{ meta.key + ': ' + meta.value }}
@@ -270,6 +270,7 @@ :selectionEnabled="false" :customOfferingsAllowed="true" :autoSelectCustomOffering="true" + :isKVMUnmanage="isKVMUnmanage" :autoSelectLabel="$t('label.auto.assign.diskoffering.disk.size')" @select-multi-disk-offering="updateMultiDiskOffering" />
From f0f4e836b5a9e06942bc895f742a03d2d9951a29 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Wed, 6 Dec 2023 19:14:48 +0530 Subject: [PATCH 55/65] fix list local storagepools by hostid --- .../com/cloud/api/query/QueryManagerImpl.java | 31 ++++++++++++------- .../vm/UnmanagedVMsManagerImpl.java | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index fd3e3298338d..1049b21d0bd5 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -34,7 +34,10 @@ import javax.inject.Inject; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.VMTemplateStoragePoolVO; +import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.host.Host; import com.cloud.host.dao.HostDao; @@ -563,6 +566,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private PublicIpQuarantineDao publicIpQuarantineDao; + @Inject + private StoragePoolHostDao storagePoolHostDao; + private SearchCriteria getMinimumCpuServiceOfferingJoinSearchCriteria(int cpu) { SearchCriteria sc = _srvOfferingJoinDao.createSearchCriteria(); SearchCriteria sc1 = _srvOfferingJoinDao.createSearchCriteria(); @@ -2807,23 +2813,24 @@ private Pair, Integer> searchForAsyncJobsInternal(ListAsync @Override public ListResponse searchForStoragePools(ListStoragePoolsCmd cmd) { - Pair, Integer> result = cmd.getHostId() != null ? searchForLocalStorages(cmd) : searchForStoragePoolsInternal(cmd); + Pair, Integer> result = (ScopeType.HOST.name().equalsIgnoreCase(cmd.getScope()) && cmd.getHostId() != null) ? + searchForLocalStorages(cmd) : searchForStoragePoolsInternal(cmd); return createStoragesPoolResponse(result); } private Pair, Integer> searchForLocalStorages(ListStoragePoolsCmd cmd) { long id = cmd.getHostId(); - String scope = ScopeType.HOST.toString(); - Pair, Integer> localStorages; - - ListHostsCmd listHostsCmd = new ListHostsCmd(); - listHostsCmd.setId(id); - Pair, Integer> hosts = searchForServersInternal(listHostsCmd); - - cmd.setScope(scope); - localStorages = searchForStoragePoolsInternal(cmd); - - return localStorages; + List localstoragePools = storagePoolHostDao.listByHostId(id); + Long[] poolIds = new Long[localstoragePools.size()]; + int i = 0; + for(StoragePoolHostVO localstoragePool : localstoragePools) { + StoragePool storagePool = storagePoolDao.findById(localstoragePool.getPoolId()); + if (storagePool != null && storagePool.isLocal()) { + poolIds[i++] = localstoragePool.getPoolId(); + } + } + List pools = _poolJoinDao.searchByIds(poolIds); + return new Pair<>(pools, pools.size()); } private ListResponse createStoragesPoolResponse(Pair, Integer> storagePools) { diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 2fbd6b708572..c71ff6a60dd0 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -1627,7 +1627,7 @@ public UserVmResponse importVm(ImportVmCmd cmd) { DiskOffering diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); if (diskOffering != null && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) { - throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with selected storage pool: %s", serviceOffering.getUuid(), storagePool.getUuid())); + throw new InvalidParameterValueException(String.format("Service offering: %s storage tags are not compatible with selected storage pool: %s", serviceOffering.getUuid(), storagePool.getUuid())); } } From 53720e24f9de69e3f4188cfeb555bc4a06bdf218 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Wed, 6 Dec 2023 20:29:31 +0530 Subject: [PATCH 56/65] fix default value selected in drop down lists --- ui/src/views/tools/ManageInstances.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index ebdeabb5296c..64992474f232 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -169,7 +169,7 @@ option.id === query[param.field])) { paramid = query[param.field] } - if (!paramid && this.options[name].length === 1) { + if (!paramid && this.options[name].length > 0) { paramid = (this.options[name])[0].id } if (paramid) { From 8738009315504bf28b3a477b7f55eff7ba2d4e65 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Thu, 7 Dec 2023 15:44:46 +0530 Subject: [PATCH 57/65] fix UI for local import --- ui/src/views/tools/ManageInstances.vue | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 64992474f232..8a99d53682be 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -760,10 +760,16 @@ export default { return this.destinationHypervisor === 'kvm' }, showPod () { - return (this.selectedSourceAction !== 'external' && this.poolscope !== 'zone') + if (this.selectedSourceAction === 'shared') { + return this.poolscope !== 'zone' + } + return (this.selectedSourceAction !== 'external') }, showCluster () { - return (this.selectedSourceAction !== 'external' && this.poolscope !== 'zone') + if (this.selectedSourceAction === 'shared') { + return this.poolscope !== 'zone' + } + return (this.selectedSourceAction !== 'external') }, showHost () { return (this.selectedSourceAction === 'local') From f694a26441dccfd19541674750b1c7f9ad71e2ac Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Fri, 8 Dec 2023 10:36:28 +0530 Subject: [PATCH 58/65] fix unit tests --- .../java/com/cloud/vm/UserVmManagerImpl.java | 2 +- .../vm/UnmanagedVMsManagerImpl.java | 1 - .../vm/UnmanagedVMsManagerImplTest.java | 142 +++++------------- 3 files changed, 35 insertions(+), 110 deletions(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 4bcf7548f6e3..121ca95e365f 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4472,6 +4472,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { // already verified for positive number rootDiskSize = Long.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE)); + VMTemplateVO templateVO = _templateDao.findById(template.getId()); if (templateVO == null) { throw new InvalidParameterValueException("Unable to look up template by id " + template.getId()); @@ -8212,7 +8213,6 @@ public UserVm importVM(final DataCenter zone, final Host host, final VirtualMach if (zone == null) { throw new InvalidParameterValueException("Unable to import virtual machine with invalid zone"); } - if (host == null && hypervisorType == HypervisorType.VMware) { throw new InvalidParameterValueException("Unable to import virtual machine with invalid host"); } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 19a2085fbe88..30438958a9a1 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -368,7 +368,6 @@ private List getAdditionalNameFilters(Cluster cluster) { if (cluster == null) { return additionalNameFilter; } - if (cluster.getHypervisorType() == Hypervisor.HypervisorType.VMware) { // VMWare considers some templates as VM and they are not filtered by VirtualMachineMO.isTemplate() List templates = templatePoolDao.listAll(); diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 27109f80048b..e78319983531 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -43,13 +43,10 @@ import com.cloud.event.ActionEventUtils; import com.cloud.event.UsageEventUtils; import com.cloud.exception.AgentUnavailableException; -import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.UnsupportedServiceException; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -140,6 +137,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -293,49 +291,50 @@ public void setUp() throws Exception { instance.setNics(instanceNics); instance.setPowerState(UnmanagedInstanceTO.PowerState.PowerOn); - clusterVO = new ClusterVO(1L, 1L, "Cluster"); - when(clusterDao.findById(anyLong())).thenReturn(clusterVO); - when(configurationDao.getValue(anyString())).thenReturn(null); - doNothing().when(resourceLimitService).checkResourceLimit(any(Account.class), any(Resource.ResourceType.class), anyLong()); + ClusterVO clusterVO = new ClusterVO(1L, 1L, "Cluster"); + clusterVO.setHypervisorType(Hypervisor.HypervisorType.VMware.toString()); + when(clusterDao.findById(Mockito.anyLong())).thenReturn(clusterVO); + when(configurationDao.getValue(Mockito.anyString())).thenReturn(null); + doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(Account.class), Mockito.any(Resource.ResourceType.class), Mockito.anyLong()); List hosts = new ArrayList<>(); HostVO hostVO = Mockito.mock(HostVO.class); when(hostVO.isInMaintenanceStates()).thenReturn(false); hosts.add(hostVO); - when(hostVO.checkHostServiceOfferingTags(any())).thenReturn(true); - when(resourceManager.listHostsInClusterByStatus(anyLong(), any(Status.class))).thenReturn(hosts); + when(hostVO.checkHostServiceOfferingTags(Mockito.any())).thenReturn(true); + when(resourceManager.listHostsInClusterByStatus(Mockito.anyLong(), Mockito.any(Status.class))).thenReturn(hosts); when(resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(any(Hypervisor.HypervisorType.class), Mockito.anyLong())).thenReturn(hosts); List templates = new ArrayList<>(); when(templatePoolDao.listAll()).thenReturn(templates); List volumes = new ArrayList<>(); - when(volumeDao.findIncludingRemovedByZone(anyLong())).thenReturn(volumes); + when(volumeDao.findIncludingRemovedByZone(Mockito.anyLong())).thenReturn(volumes); GetUnmanagedInstancesCommand cmd = Mockito.mock(GetUnmanagedInstancesCommand.class); HashMap map = new HashMap<>(); map.put(instance.getName(), instance); Answer answer = new GetUnmanagedInstancesAnswer(cmd, "", map); - when(agentManager.easySend(anyLong(), any(GetUnmanagedInstancesCommand.class))).thenReturn(answer); + when(agentManager.easySend(Mockito.anyLong(), Mockito.any(GetUnmanagedInstancesCommand.class))).thenReturn(answer); GetRemoteVmsCommand remoteVmListcmd = Mockito.mock(GetRemoteVmsCommand.class); Answer remoteVmListAnswer = new GetRemoteVmsAnswer(remoteVmListcmd, "", map); when(agentManager.easySend(Mockito.anyLong(), any(GetRemoteVmsCommand.class))).thenReturn(remoteVmListAnswer); DataCenterVO zone = Mockito.mock(DataCenterVO.class); when(zone.getId()).thenReturn(1L); - when(dataCenterDao.findById(anyLong())).thenReturn(zone); - when(accountService.getActiveAccountById(anyLong())).thenReturn(Mockito.mock(Account.class)); + when(dataCenterDao.findById(Mockito.anyLong())).thenReturn(zone); + when(accountService.getActiveAccountById(Mockito.anyLong())).thenReturn(Mockito.mock(Account.class)); List users = new ArrayList<>(); users.add(Mockito.mock(UserVO.class)); - when(userDao.listByAccount(anyLong())).thenReturn(users); + when(userDao.listByAccount(Mockito.anyLong())).thenReturn(users); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); when(template.getName()).thenReturn("Template"); - when(templateDao.findById(anyLong())).thenReturn(template); + when(templateDao.findById(Mockito.anyLong())).thenReturn(template); ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); when(serviceOffering.getId()).thenReturn(1L); when(serviceOffering.isDynamic()).thenReturn(false); when(serviceOffering.getCpu()).thenReturn(instance.getCpuCores()); when(serviceOffering.getRamSize()).thenReturn(instance.getMemory()); when(serviceOffering.getSpeed()).thenReturn(instance.getCpuSpeed()); - when(serviceOfferingDao.findById(anyLong())).thenReturn(serviceOffering); + when(serviceOfferingDao.findById(Mockito.anyLong())).thenReturn(serviceOffering); DiskOfferingVO diskOfferingVO = Mockito.mock(DiskOfferingVO.class); - when(diskOfferingDao.findById(anyLong())).thenReturn(diskOfferingVO); - userVm = Mockito.mock(UserVmVO.class); + when(diskOfferingDao.findById(Mockito.anyLong())).thenReturn(diskOfferingVO); + UserVmVO userVm = Mockito.mock(UserVmVO.class); when(userVm.getAccountId()).thenReturn(1L); when(userVm.getDataCenterId()).thenReturn(1L); when(userVm.getHostName()).thenReturn(instance.getName()); @@ -351,29 +350,31 @@ public void setUp() throws Exception { when(poolVO.getClusterId()).thenReturn(clusterVO.getId()); List pools = new ArrayList<>(); pools.add(poolVO); - when(primaryDataStoreDao.listPoolByHostPath(anyString(), anyString())).thenReturn(pools); + when(primaryDataStoreDao.listPoolByHostPath(Mockito.anyString(), Mockito.anyString())).thenReturn(pools); when(userVmManager.importVM(nullable(DataCenter.class), nullable(Host.class), nullable(VirtualMachineTemplate.class), nullable(String.class), nullable(String.class), nullable(Account.class), nullable(String.class), nullable(Account.class), nullable(Boolean.class), nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(ServiceOffering.class), nullable(String.class), nullable(String.class), nullable(Hypervisor.HypervisorType.class), nullable(Map.class), nullable(VirtualMachine.PowerState.class), nullable(LinkedHashMap.class))).thenReturn(userVm); NetworkVO networkVO = Mockito.mock(NetworkVO.class); when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2); + when(networkVO.getBroadcastUri()).thenReturn(URI.create(String.format("vlan://%d", instanceNic.getVlan()))); when(networkVO.getDataCenterId()).thenReturn(1L); - when(networkDao.findById(anyLong())).thenReturn(networkVO); + when(networkDao.findById(Mockito.anyLong())).thenReturn(networkVO); List networks = new ArrayList<>(); networks.add(networkVO); - when(networkDao.listByZone(anyLong())).thenReturn(networks); - doNothing().when(networkModel).checkNetworkPermissions(any(Account.class), any(Network.class)); + when(networkDao.listByZone(Mockito.anyLong())).thenReturn(networks); + doNothing().when(networkModel).checkNetworkPermissions(Mockito.any(Account.class), Mockito.any(Network.class)); NicProfile profile = Mockito.mock(NicProfile.class); Integer deviceId = 100; Pair pair = new Pair(profile, deviceId); when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), Mockito.anyBoolean())).thenReturn(pair); - when(volumeDao.findByInstance(anyLong())).thenReturn(volumes); + when(volumeDao.findByInstance(Mockito.anyLong())).thenReturn(volumes); List userVmResponses = new ArrayList<>(); UserVmResponse userVmResponse = new UserVmResponse(); userVmResponse.setInstanceName(instance.getName()); userVmResponses.add(userVmResponse); - when(responseGenerator.createUserVmResponse(any(ResponseObject.ResponseView.class), anyString(), any(UserVm.class))).thenReturn(userVmResponses); + when(responseGenerator.createUserVmResponse(Mockito.any(ResponseObject.ResponseView.class), Mockito.anyString(), Mockito.any(UserVm.class))).thenReturn(userVmResponses); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Running); } @@ -387,14 +388,6 @@ public void tearDown() throws Exception { @Test public void listUnmanagedInstancesTest() { - testListUnmanagedInstancesTest(Hypervisor.HypervisorType.VMware); - testListUnmanagedInstancesTest(Hypervisor.HypervisorType.KVM); - } - - private void testListUnmanagedInstancesTest(Hypervisor.HypervisorType hypervisorType) { - clusterVO.setHypervisorType(hypervisorType.toString()); - when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); - when(userVm.getHypervisorType()).thenReturn(hypervisorType); ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class); unmanagedVMsManager.listUnmanagedInstances(cmd); } @@ -404,19 +397,13 @@ public void listUnmanagedInstancesInvalidHypervisorTest() { ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class); ClusterVO cluster = new ClusterVO(1, 1, "Cluster"); cluster.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString()); - when(clusterDao.findById(anyLong())).thenReturn(cluster); + when(clusterDao.findById(Mockito.anyLong())).thenReturn(cluster); unmanagedVMsManager.listUnmanagedInstances(cmd); } @Test(expected = PermissionDeniedException.class) public void listUnmanagedInstancesInvalidCallerTest() { - testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorType.VMware); - testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorType.KVM); - } - - private void testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorType hypervisorType) { CallContext.unregister(); - clusterVO.setHypervisorType(hypervisorType.toString()); AccountVO account = new AccountVO("user", 1L, "", Account.Type.NORMAL, "uuid"); UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); CallContext.register(user, account); @@ -425,31 +412,17 @@ private void testListUnmanagedInstancesInvalidCallerTest(Hypervisor.HypervisorTy } @Test - public void importUnmanagedInstanceTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - testImportUnmanagedInstanceTest(Hypervisor.HypervisorType.VMware); - testImportUnmanagedInstanceTest(Hypervisor.HypervisorType.KVM); - } - - private void testImportUnmanagedInstanceTest(Hypervisor.HypervisorType hypervisorType) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - clusterVO.setHypervisorType(hypervisorType.toString()); - when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); - when(userVm.getHypervisorType()).thenReturn(hypervisorType); + public void importUnmanagedInstanceTest() { ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); - try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class);) { + try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd); } } @Test(expected = InvalidParameterValueException.class) - public void importUnmanagedInstanceInvalidHostnameTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.HypervisorType.VMware); - testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.HypervisorType.KVM); - } - - private void testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.HypervisorType hypervisorType) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - clusterVO.setHypervisorType(hypervisorType.toString()); + public void importUnmanagedInstanceInvalidHostnameTest() { ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getName()).thenReturn("some name"); @@ -457,13 +430,7 @@ private void testImportUnmanagedInstanceInvalidHostnameTest(Hypervisor.Hyperviso } @Test(expected = ServerApiException.class) - public void importUnmanagedInstanceMissingInstanceTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.HypervisorType.VMware); - testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.HypervisorType.KVM); - } - - private void testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.HypervisorType hypervisorType) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - clusterVO.setHypervisorType(hypervisorType.toString()); + public void importUnmanagedInstanceMissingInstanceTest() { ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("SomeInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); @@ -472,75 +439,37 @@ private void testImportUnmanagedInstanceMissingInstanceTest(Hypervisor.Hyperviso @Test(expected = InvalidParameterValueException.class) public void unmanageVMInstanceMissingInstanceTest() { - testUnmanageVMInstanceMissingInstanceTest(Hypervisor.HypervisorType.VMware); - testUnmanageVMInstanceMissingInstanceTest(Hypervisor.HypervisorType.KVM); - - } - - private void testUnmanageVMInstanceMissingInstanceTest(Hypervisor.HypervisorType hypervisorType) { - clusterVO.setHypervisorType(hypervisorType.toString()); long notExistingId = 10L; unmanagedVMsManager.unmanageVMInstance(notExistingId); } @Test(expected = InvalidParameterValueException.class) public void unmanageVMInstanceDestroyedInstanceTest() { - testUnmanageVMInstanceDestroyedInstanceTest(Hypervisor.HypervisorType.VMware); - testUnmanageVMInstanceDestroyedInstanceTest(Hypervisor.HypervisorType.KVM); - } - - private void testUnmanageVMInstanceDestroyedInstanceTest(Hypervisor.HypervisorType hypervisorType){ - clusterVO.setHypervisorType(hypervisorType.toString()); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Destroyed); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = InvalidParameterValueException.class) public void unmanageVMInstanceExpungedInstanceTest() { - testUnmanageVMInstanceExpungedInstanceTest(Hypervisor.HypervisorType.VMware); - testUnmanageVMInstanceExpungedInstanceTest(Hypervisor.HypervisorType.KVM); - } - - private void testUnmanageVMInstanceExpungedInstanceTest(Hypervisor.HypervisorType hypervisorType){ - clusterVO.setHypervisorType(hypervisorType.toString()); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Expunging); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = UnsupportedServiceException.class) public void unmanageVMInstanceExistingVMSnapshotsTest() { - testUnmanageVMInstanceExistingVMSnapshotsTest(Hypervisor.HypervisorType.VMware); - testUnmanageVMInstanceExistingVMSnapshotsTest(Hypervisor.HypervisorType.KVM); - } - - private void testUnmanageVMInstanceExistingVMSnapshotsTest(Hypervisor.HypervisorType hypervisorType) { - clusterVO.setHypervisorType(hypervisorType.toString()); - when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = UnsupportedServiceException.class) public void unmanageVMInstanceExistingVolumeSnapshotsTest() { - testUnmanageVMInstanceExistingVolumeSnapshotsTest(Hypervisor.HypervisorType.VMware); - testUnmanageVMInstanceExistingVolumeSnapshotsTest(Hypervisor.HypervisorType.KVM); - } - - private void testUnmanageVMInstanceExistingVolumeSnapshotsTest(Hypervisor.HypervisorType hypervisorType){ - clusterVO.setHypervisorType(hypervisorType.toString()); - when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @Test(expected = UnsupportedServiceException.class) public void unmanageVMInstanceExistingISOAttachedTest() { - testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.HypervisorType.VMware); - testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.HypervisorType.KVM); - } - - private void testUnmanageVMInstanceExistingISOAttachedTest(Hypervisor.HypervisorType hypervisorType) { - clusterVO.setHypervisorType(hypervisorType.toString()); - when(virtualMachine.getHypervisorType()).thenReturn(hypervisorType); - UserVmVO userVmVO = mock(UserVmVO.class); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None); unmanagedVMsManager.unmanageVMInstance(virtualMachineId); } @@ -672,7 +601,7 @@ private void baseTestImportVmFromVmwareToKvm(VcenterParameter vcenterParameter, when(importVmCmd.getClusterId()).thenReturn(clusterId); when(importVmCmd.getDomainId()).thenReturn(null); when(importVmCmd.getImportSource()).thenReturn(VmImportService.ImportSource.VMWARE.toString()); - when(importVmCmd.getHost()).thenReturn(host); + when(importVmCmd.getHostIp()).thenReturn(host); when(importVmCmd.getNicNetworkList()).thenReturn(Map.of("NIC 1", networkId)); when(importVmCmd.getConvertInstanceHostId()).thenReturn(null); when(importVmCmd.getConvertStoragePoolId()).thenReturn(null); @@ -698,9 +627,6 @@ private void baseTestImportVmFromVmwareToKvm(VcenterParameter vcenterParameter, if (selectConvertHost) { when(importVmCmd.getConvertInstanceHostId()).thenReturn(convertHostId); when(hostDao.findById(convertHostId)).thenReturn(convertHost); - } else { - when(hostDao.listByClusterAndHypervisorType(clusterId, Hypervisor.HypervisorType.KVM)) - .thenReturn(List.of(convertHost)); } DataStoreTO dataStoreTO = mock(DataStoreTO.class); From 3abde5504d1012abaf5768435cee15b8afc49b6f Mon Sep 17 00:00:00 2001 From: nvazquez Date: Fri, 8 Dec 2023 14:11:13 -0300 Subject: [PATCH 59/65] Fix missing Inject annotation --- .../java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 30438958a9a1..b7190f4ff217 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -267,6 +267,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { private IpAddressManager ipAddressManager; @Inject private StoragePoolHostDao storagePoolHostDao; + @Inject private HypervisorGuruManager hypervisorGuruManager; @Inject private VmwareDatacenterDao vmwareDatacenterDao; From d9778581f5f9916d5d463b1daf7b1525bb95b6cf Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 11 Dec 2023 12:57:29 -0300 Subject: [PATCH 60/65] Move Fetch Instances button position --- ui/src/views/tools/ManageInstances.vue | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 0c40f31116a4..7877e9b30fd0 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -294,16 +294,6 @@ v-model:value="form.diskpath" > - -
- - {{ $t('label.fetch.instances') }} - -
-
+ + +
+ + {{ $t('label.fetch.instances') }} + +
+
+
From a1bcac33726ad0dfc72089cfe2f5c8a21fd611af Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Tue, 12 Dec 2023 02:00:51 +0530 Subject: [PATCH 61/65] fix ram unit and disk size --- .../LibvirtGetRemoteVmsCommandWrapper.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index adf81c275062..a9baedee28f9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -31,6 +31,9 @@ import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.log4j.Logger; import org.libvirt.Connect; @@ -41,6 +44,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; @ResourceWrapper(handles = GetRemoteVmsCommand.class) public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper { @@ -90,7 +94,7 @@ private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvir instance.setCpuCoresPerSocket(parser.getCpuModeDef().getCoresPerSocket()); } Long memory = domain.getMaxMemory(); - instance.setMemory(memory.intValue()); + instance.setMemory(memory.intValue()/1024); if (parser.getCpuTuneDef() !=null) { instance.setCpuSpeed(parser.getCpuTuneDef().getShares()); } @@ -145,7 +149,20 @@ private List getUnmanagedInstanceDisks(List info = qemu.info(file); + size = Long.parseLong(info.getOrDefault("virtual_size", "0")); + imagePath = info.getOrDefault("image", null); + } catch (QemuImgException | LibvirtException e) { + throw new RuntimeException(e); + } + + disk.setCapacity(size); disk.setDiskId(String.valueOf(counter++)); disk.setLabel(diskDef.getDiskLabel()); disk.setController(diskDef.getBusType().toString()); @@ -162,6 +179,8 @@ private List getUnmanagedInstanceDisks(List Date: Tue, 12 Dec 2023 03:47:15 +0530 Subject: [PATCH 62/65] Ignore Vms with exceptions --- .../wrapper/LibvirtGetRemoteVmsCommandWrapper.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index a9baedee28f9..1cfdcb87a71d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -70,8 +70,12 @@ public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingR s_logger.debug("VM " + domain.getName() + ": powerstate = " + ps + "; vm state=" + state.toString()); if (state == VirtualMachine.PowerState.PowerOff) { - UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn); - unmanagedInstances.put(instance.getName(), instance); + try { + UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn); + unmanagedInstances.put(instance.getName(), instance); + } catch (Exception e) { + s_logger.error("Error while fetching instance details", e); + } } domain.free(); } From 51f9d59310b3815694dd7bda59d90fbcea51349f Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Tue, 12 Dec 2023 10:59:40 +0530 Subject: [PATCH 63/65] Use blockinfo instead of qemu-img to fetch remote disk size --- .../LibvirtGetRemoteVmsCommandWrapper.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index 1cfdcb87a71d..700f058b59b8 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -31,20 +31,17 @@ import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; -import org.apache.cloudstack.utils.qemu.QemuImg; -import org.apache.cloudstack.utils.qemu.QemuImgException; -import org.apache.cloudstack.utils.qemu.QemuImgFile; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.log4j.Logger; import org.libvirt.Connect; import org.libvirt.Domain; +import org.libvirt.DomainBlockInfo; import org.libvirt.DomainInfo; import org.libvirt.LibvirtException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; @ResourceWrapper(handles = GetRemoteVmsCommand.class) public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper { @@ -104,7 +101,7 @@ private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvir } instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName()))); instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces())); - instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource)); + instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource, domain)); instance.setVncPassword(parser.getVncPasswd() + "aaaaaaaaaaaaaa"); // Suffix back extra characters for DB compatibility return instance; @@ -142,7 +139,9 @@ private List getUnmanagedInstanceNics(List getUnmanagedInstanceDisks(List disksInfo, LibvirtComputingResource libvirtComputingResource){ + private List getUnmanagedInstanceDisks(List disksInfo, + LibvirtComputingResource libvirtComputingResource, + Domain dm){ final ArrayList disks = new ArrayList<>(disksInfo.size()); int counter = 0; for (LibvirtVMDef.DiskDef diskDef : disksInfo) { @@ -155,14 +154,10 @@ private List getUnmanagedInstanceDisks(List info = qemu.info(file); - size = Long.parseLong(info.getOrDefault("virtual_size", "0")); - imagePath = info.getOrDefault("image", null); - } catch (QemuImgException | LibvirtException e) { + DomainBlockInfo blockInfo = dm.blockInfo(diskDef.getSourcePath()); + size = blockInfo.getCapacity(); + } catch (LibvirtException e) { throw new RuntimeException(e); } @@ -183,8 +178,6 @@ private List getUnmanagedInstanceDisks(List Date: Tue, 12 Dec 2023 11:41:45 +0530 Subject: [PATCH 64/65] fix UI for ROOT disk selection --- .../views/tools/ImportUnmanagedInstance.vue | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index e3a1e46b28b4..302125c2e813 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -258,7 +258,7 @@ {{ record.displaytext || record.name }}
- {{ meta.key + ': ' + meta.value }} + {{ meta.key + ': ' + meta.value }}
@@ -488,7 +488,16 @@ export default { ], storagePoolsForConversion: [], selectedStorageOptionForConversion: null, - selectedStoragePoolForConversion: null + selectedStoragePoolForConversion: null, + showStoragePoolsForConversion: false, + selectedRootDiskColumns: [ + { + key: 'name', + dataIndex: 'name', + title: this.$t('label.rootdisk') + } + ], + selectedRootDiskSources: [] } }, beforeCreate () { @@ -694,6 +703,9 @@ export default { page: 1 }) this.fetchKvmHostsForConversion() + if (this.resource.disk.length > 1) { + this.updateSelectedRootDisk() + } }, getMeta (obj, metaKeys) { var meta = [] @@ -942,6 +954,17 @@ export default { } ] }, + onSelectRootDisk (val) { + this.selectedRootDiskIndex = val + this.updateSelectedRootDisk() + }, + updateSelectedRootDisk () { + var rootDisk = this.resource.disk[this.selectedRootDiskIndex] + rootDisk.size = rootDisk.capacity / (1024 * 1024 * 1024) + rootDisk.name = `${rootDisk.label} (${rootDisk.size} GB)` + rootDisk.meta = this.getMeta(rootDisk, { controller: 'controller', datastorename: 'datastore', position: 'position' }) + this.selectedRootDiskSources = [rootDisk] + }, handleSubmit (e) { e.preventDefault() if (this.loading) return From 34ace3153f46afee46bccf3992b7719f2d81ab5b Mon Sep 17 00:00:00 2001 From: nvazquez Date: Tue, 12 Dec 2023 12:59:26 -0300 Subject: [PATCH 65/65] Align fetch instances button to the card content --- ui/src/views/tools/ManageInstances.vue | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 7877e9b30fd0..fc14f684e724 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -148,7 +148,6 @@ >
-
@@ -307,13 +306,13 @@ - -
+ +
- {{ $t('label.fetch.instances') }} + shape="round" + type="primary" + @click="() => { fetchExtKVMInstances() }"> + {{ $t('label.fetch.instances') }}
@@ -1453,6 +1452,11 @@ export default { .action-button-right { text-align: right; } +.fetch-instances-column { + width: 50%; + margin-left: 50%; + padding-left: 24px; +} .breadcrumb-card { margin-left: -24px;