New Feature: Import VMware VMs into KVM (#7881)

This PR adds the capability in CloudStack to convert VMware Instances disk(s) to KVM using virt-v2v and import them as CloudStack instances. It enables CloudStack operators to import VMware instances from vSphere into a KVM cluster managed by CloudStack. vSphere/VMware setup might be managed by CloudStack or be a standalone setup.

    CloudStack will let the administrator select a VM from an existing VMware vCenter in the CloudStack environment or external vCenter requesting vCenter IP, Datacenter name and credentials.
    The migrated VM will be imported as a KVM instance
    The migration is done through virt-v2v: https://access.redhat.com/articles/1351473, https://www.ovirt.org/develop/release-management/features/virt/virt-v2v-integration.html
    The migration process timeout can be set by the setting convert.instance.process.timeout
    Before attempting the virt-v2v migration, CloudStack will create a clone of the source VM on VMware. The clone VM will be removed after the registration process finishes.
    CloudStack will delegate the migration action to a KVM host and the host will attempt to migrate the VM invoking virt-v2v. In case the guest OS is not supported then CloudStack will handle the error operation as a failure
    The migration process using virt-v2v may not be a fast process
    CloudStack will not perform any check about the guest OS compatibility for the virt-v2v library as indicated on: https://access.redhat.com/articles/1351473.
This commit is contained in:
Nicolas Vazquez 2023-12-07 04:29:56 -03:00 committed by GitHub
parent fdfbb4fad1
commit 371ad9f55b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 3919 additions and 668 deletions

View File

@ -419,3 +419,6 @@ iscsi.session.cleanup.enabled=false
# Timeout (in milliseconds) of the KVM heartbeat checker.
# kvm.heartbeat.checker.timeout=360000
# Instance Conversion from Vmware to KVM through virt-v2v. Enable verbose mode
# virtv2v.verbose.enabled=false

View File

@ -733,6 +733,13 @@ public class AgentProperties{
*/
public static final Property<Integer> IOTHREADS = new Property<>("iothreads", 1);
/**
* Enable verbose mode for virt-v2v Instance Conversion from Vmware to KVM
* Data type: Boolean.<br>
* Default value: <code>false</code>
*/
public static final Property<Boolean> VIRTV2V_VERBOSE_ENABLED = new Property<>("virtv2v.verbose.enabled", false);
/**
* BGP controll CIDR
* Data type: String.<br>

View File

@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.cloud.agent.api.to;
import com.cloud.agent.api.LogLevel;
import com.cloud.hypervisor.Hypervisor;
import java.io.Serializable;
public class RemoteInstanceTO implements Serializable {
private Hypervisor.HypervisorType hypervisorType;
private String hostName;
private String instanceName;
// Vmware Remote Instances parameters
// TODO: cloud.agent.transport.Request#getCommands() cannot handle gsoc decode for polymorphic classes
private String vcenterUsername;
@LogLevel(LogLevel.Log4jLevel.Off)
private String vcenterPassword;
private String vcenterHost;
private String datacenterName;
private String clusterName;
public RemoteInstanceTO() {
}
public RemoteInstanceTO(String hostName, String instanceName, String vcenterHost,
String datacenterName, String clusterName,
String vcenterUsername, String vcenterPassword) {
this.hypervisorType = Hypervisor.HypervisorType.VMware;
this.hostName = hostName;
this.instanceName = instanceName;
this.vcenterHost = vcenterHost;
this.datacenterName = datacenterName;
this.clusterName = clusterName;
this.vcenterUsername = vcenterUsername;
this.vcenterPassword = vcenterPassword;
}
public Hypervisor.HypervisorType getHypervisorType() {
return this.hypervisorType;
}
public String getInstanceName() {
return this.instanceName;
}
public String getHostName() {
return this.hostName;
}
public String getVcenterUsername() {
return vcenterUsername;
}
public String getVcenterPassword() {
return vcenterPassword;
}
public String getVcenterHost() {
return vcenterHost;
}
public String getDatacenterName() {
return datacenterName;
}
public String getClusterName() {
return clusterName;
}
}

View File

@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor.vmware;
package com.cloud.dc;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;

View File

@ -33,6 +33,7 @@ import com.cloud.utils.component.Adapter;
import com.cloud.vm.NicProfile;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
public interface HypervisorGuru extends Adapter {
@ -104,4 +105,25 @@ public interface HypervisorGuru extends Adapter {
* @return a list of commands to perform for a successful migration
*/
List<Command> finalizeMigrate(VirtualMachine vm, Map<Volume, StoragePool> volumeToPool);
/**
* Will perform a clone of a VM on an external host (if the guru can handle)
* @param hostIp VM's source host IP
* @param vmName name of the source VM to clone from
* @param params hypervisor specific additional parameters
* @return a reference to the cloned VM
*/
UnmanagedInstanceTO cloneHypervisorVMOutOfBand(String hostIp, String vmName,
Map<String, String> params);
/**
* Removes a VM created as a clone of a VM on an external host
* @param hostIp VM's source host IP
* @param vmName name of the VM to remove
* @param params hypervisor specific additional parameters
* @return true if the operation succeeds, false if not
*/
boolean removeClonedHypervisorVMOutOfBand(String hostIp, String vmName,
Map<String, String> params);
}

View File

@ -86,4 +86,16 @@ public interface VmDetailConstants {
String DEPLOY_AS_IS_CONFIGURATION = "configurationId";
String KEY_PAIR_NAMES = "keypairnames";
String CKS_CONTROL_NODE_LOGIN_USER = "controlNodeLoginUser";
// VMware to KVM VM migrations specific
String VMWARE_TO_KVM_PREFIX = "vmware-to-kvm";
String VMWARE_VCENTER_HOST = String.format("%s-vcenter", VMWARE_TO_KVM_PREFIX);
String VMWARE_DATACENTER_NAME = String.format("%s-datacenter", VMWARE_TO_KVM_PREFIX);
String VMWARE_CLUSTER_NAME = String.format("%s-cluster", VMWARE_TO_KVM_PREFIX);
String VMWARE_VCENTER_USERNAME = String.format("%s-username", VMWARE_TO_KVM_PREFIX);
String VMWARE_VCENTER_PASSWORD = String.format("%s-password", VMWARE_TO_KVM_PREFIX);
String VMWARE_VM_NAME = String.format("%s-vmname", VMWARE_TO_KVM_PREFIX);
String VMWARE_HOST_NAME = String.format("%s-host", VMWARE_TO_KVM_PREFIX);
String VMWARE_DISK = String.format("%s-disk", VMWARE_TO_KVM_PREFIX);
String VMWARE_MAC_ADDRESSES = String.format("%s-mac-addresses", VMWARE_TO_KVM_PREFIX);
}

View File

@ -69,6 +69,8 @@ public class ApiConstants {
public static final String CERTIFICATE_SERIALNUM = "serialnum";
public static final String CERTIFICATE_SUBJECT = "subject";
public static final String CERTIFICATE_VALIDITY = "validity";
public static final String CONVERT_INSTANCE_HOST_ID = "convertinstancehostid";
public static final String CONVERT_INSTANCE_STORAGE_POOL_ID = "convertinstancepoolid";
public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck";
public static final String CONTROLLER = "controller";
public static final String CONTROLLER_UNIT = "controllerunit";
@ -120,6 +122,7 @@ public class ApiConstants {
public static final String MIN_IOPS = "miniops";
public static final String MAX_IOPS = "maxiops";
public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve";
public static final String DATACENTER_NAME = "datacentername";
public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist";
public static final String DEFAULT_VALUE = "defaultvalue";
public static final String DESCRIPTION = "description";
@ -207,6 +210,7 @@ public class ApiConstants {
public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage";
public static final String HOST_ID = "hostid";
public static final String HOST_IDS = "hostids";
public static final String HOST_IP = "hostip";
public static final String HOST_NAME = "hostname";
public static final String HOST_CONTROL_STATE = "hostcontrolstate";
public static final String HOSTS_MAP = "hostsmap";
@ -779,6 +783,7 @@ public class ApiConstants {
public static final String VSM_CONFIG_STATE = "vsmconfigstate";
public static final String VSM_DEVICE_STATE = "vsmdevicestate";
public static final String VCENTER = "vcenter";
public static final String EXISTING_VCENTER_ID = "existingvcenterid";
public static final String ADD_VSM_FLAG = "addvsmflag";
public static final String END_POINT = "endpoint";
public static final String REGION_ID = "regionid";
@ -1059,6 +1064,7 @@ public class ApiConstants {
public static final String SOURCE_NAT_IP = "sourcenatipaddress";
public static final String SOURCE_NAT_IP_ID = "sourcenatipaddressid";
public static final String HAS_RULES = "hasrules";
public static final String IMPORT_SOURCE = "importsource";
public static final String OBJECT_STORAGE = "objectstore";
public static final String HEURISTIC_RULE = "heuristicrule";

View File

@ -123,6 +123,7 @@ import org.apache.cloudstack.api.response.TemplatePermissionsResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.TrafficMonitorResponse;
import org.apache.cloudstack.api.response.TrafficTypeResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.api.response.UserDataResponse;
@ -237,6 +238,7 @@ import com.cloud.vm.Nic;
import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.snapshot.VMSnapshot;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
public interface ResponseGenerator {
UserResponse createUserResponse(UserAccount user);
@ -538,6 +540,8 @@ public interface ResponseGenerator {
FirewallResponse createIpv6FirewallRuleResponse(FirewallRule acl);
UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host);
SecondaryStorageHeuristicsResponse createSecondaryStorageSelectorResponse(Heuristic heuristic);
IpQuarantineResponse createQuarantinedIpsResponse(PublicIpQuarantine publicIp);

View File

@ -124,7 +124,7 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
type = CommandType.UUID,
entityType = ServiceOfferingResponse.class,
required = true,
description = "the ID of the service offering for the virtual machine")
description = "the service offering for the virtual machine")
private Long serviceOfferingId;
@Parameter(name = ApiConstants.NIC_NETWORK_LIST,
@ -154,7 +154,7 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.FORCED,
type = CommandType.BOOLEAN,
description = "VM is imported despite some of its NIC's MAC addresses are already present")
description = "VM is imported despite some of its NIC's MAC addresses are already present, in case the MAC address exists then a new MAC address is generated")
private Boolean forced;
/////////////////////////////////////////////////////
@ -279,7 +279,8 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
@Override
public String getEventDescription() {
return "Importing unmanaged VM";
String vmName = this.name;
return String.format("Importing unmanaged VM: %s", vmName);
}
public boolean isForced() {

View File

@ -0,0 +1,180 @@
// 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.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VmwareDatacenterResponse;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
@APICommand(name = "importVm",
description = "Import virtual machine from a unmanaged host into CloudStack",
responseObject = UserVmResponse.class,
responseView = ResponseObject.ResponseView.Full,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.Admin},
since = "4.19.0")
public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
public static final Logger LOGGER = Logger.getLogger(ImportVmCmd.class);
@Parameter(name = ApiConstants.HYPERVISOR,
type = CommandType.STRING,
required = true,
description = "hypervisor type of the host")
private String hypervisor;
@Parameter(name = ApiConstants.IMPORT_SOURCE,
type = CommandType.STRING,
required = true,
description = "Source location for Import" )
private String importSource;
// Import from Vmware to KVM migration parameters
@Parameter(name = ApiConstants.EXISTING_VCENTER_ID,
type = CommandType.UUID,
entityType = VmwareDatacenterResponse.class,
description = "(only for importing migrated VMs from Vmware to KVM) UUID of a linked existing vCenter")
private Long existingVcenterId;
@Parameter(name = ApiConstants.HOST_IP,
type = BaseCmd.CommandType.STRING,
description = "(only for importing migrated VMs from Vmware to KVM) VMware ESXi host IP/Name.")
private String host;
@Parameter(name = ApiConstants.VCENTER,
type = CommandType.STRING,
description = "(only for importing migrated VMs from Vmware to KVM) The name/ip of vCenter. Make sure it is IP address or full qualified domain name for host running vCenter server.")
private String vcenter;
@Parameter(name = ApiConstants.DATACENTER_NAME, type = CommandType.STRING,
description = "(only for importing migrated VMs from Vmware to KVM) Name of VMware datacenter.")
private String datacenterName;
@Parameter(name = ApiConstants.CLUSTER_NAME, type = CommandType.STRING,
description = "(only for importing migrated VMs from Vmware to KVM) Name of VMware cluster.")
private String clusterName;
@Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING,
description = "(only for importing migrated VMs from Vmware to KVM) The Username required to connect to resource.")
private String username;
@Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING,
description = "(only for importing migrated VMs from Vmware to KVM) The password for the specified username.")
private String password;
@Parameter(name = ApiConstants.CONVERT_INSTANCE_HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
description = "(only for importing migrated VMs from Vmware to KVM) optional - the host to perform the virt-v2v migration from VMware to KVM.")
private Long convertInstanceHostId;
@Parameter(name = ApiConstants.CONVERT_INSTANCE_STORAGE_POOL_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class,
description = "(only for importing migrated VMs from Vmware to KVM) optional - the temporary storage pool to perform the virt-v2v migration from VMware to KVM.")
private Long convertStoragePoolId;
@Override
public String getEventType() {
return EventTypes.EVENT_VM_IMPORT;
}
@Override
public String getEventDescription() {
String vmName = getName();
if (ObjectUtils.anyNotNull(vcenter, existingVcenterId)) {
String msg = StringUtils.isNotBlank(vcenter) ?
String.format("external vCenter: %s - datacenter: %s", vcenter, datacenterName) :
String.format("existing vCenter Datacenter with ID: %s", existingVcenterId);
return String.format("Importing unmanaged VM: %s from %s - VM: %s", getDisplayName(), msg, vmName);
}
return String.format("Importing unmanaged VM: %s", vmName);
}
public Long getExistingVcenterId() {
return existingVcenterId;
}
public String getHost() {
return host;
}
public String getVcenter() {
return vcenter;
}
public String getDatacenterName() {
return datacenterName;
}
public String getClusterName() {
return clusterName;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public Long getConvertInstanceHostId() {
return convertInstanceHostId;
}
public Long getConvertStoragePoolId() {
return convertStoragePoolId;
}
public String getHypervisor() {
return hypervisor;
}
public String getImportSource() {
return importSource;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
UserVmResponse response = vmImportService.importVm(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -39,6 +39,10 @@ public class UnmanagedInstanceResponse extends BaseResponse {
@Param(description = "the ID of the cluster to which virtual machine belongs")
private String clusterId;
@SerializedName(ApiConstants.CLUSTER_NAME)
@Param(description = "the name of the cluster to which virtual machine belongs")
private String clusterName;
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "the ID of the host to which virtual machine belongs")
private String hostId;
@ -104,6 +108,14 @@ public class UnmanagedInstanceResponse extends BaseResponse {
this.clusterId = clusterId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getHostId() {
return hostId;
}

View File

@ -23,7 +23,7 @@ import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import com.cloud.hypervisor.vmware.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.serializer.Param;
@EntityReference(value = VmwareDatacenter.class)

View File

@ -33,6 +33,8 @@ public class UnmanagedInstanceTO {
private PowerState powerState;
private PowerState cloneSourcePowerState;
private Integer cpuCores;
private Integer cpuCoresPerSocket;
@ -45,6 +47,10 @@ public class UnmanagedInstanceTO {
private String operatingSystem;
private String clusterName;
private String hostName;
private List<Disk> disks;
private List<Nic> nics;
@ -73,6 +79,14 @@ public class UnmanagedInstanceTO {
this.powerState = powerState;
}
public PowerState getCloneSourcePowerState() {
return cloneSourcePowerState;
}
public void setCloneSourcePowerState(PowerState cloneSourcePowerState) {
this.cloneSourcePowerState = cloneSourcePowerState;
}
public Integer getCpuCores() {
return cpuCores;
}
@ -121,6 +135,22 @@ public class UnmanagedInstanceTO {
this.operatingSystem = operatingSystem;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public List<Disk> getDisks() {
return disks;
}

View File

@ -18,12 +18,24 @@
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.response.ListResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
public interface VmImportService {
enum ImportSource {
UNMANAGED, VMWARE, EXTERNAL, SHARED, LOCAL;
@Override
public String toString() {
return name().toLowerCase();
}
}
ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd);
UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd);
UserVmResponse importVm(ImportVmCmd cmd);
}

View File

@ -0,0 +1,40 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.agent.api;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
public class ConvertInstanceAnswer extends Answer {
public ConvertInstanceAnswer() {
super();
}
private UnmanagedInstanceTO convertedInstance;
public ConvertInstanceAnswer(Command command, boolean success, String details) {
super(command, success, details);
}
public ConvertInstanceAnswer(Command command, UnmanagedInstanceTO convertedInstance) {
super(command, true, "");
this.convertedInstance = convertedInstance;
}
public UnmanagedInstanceTO getConvertedInstance() {
return convertedInstance;
}
}

View File

@ -0,0 +1,63 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.agent.api;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.RemoteInstanceTO;
import com.cloud.hypervisor.Hypervisor;
import java.util.List;
public class ConvertInstanceCommand extends Command {
private RemoteInstanceTO sourceInstance;
private Hypervisor.HypervisorType destinationHypervisorType;
private List<String> destinationStoragePools;
private DataStoreTO conversionTemporaryLocation;
public ConvertInstanceCommand() {
}
public ConvertInstanceCommand(RemoteInstanceTO sourceInstance, Hypervisor.HypervisorType destinationHypervisorType,
List<String> destinationStoragePools, DataStoreTO conversionTemporaryLocation) {
this.sourceInstance = sourceInstance;
this.destinationHypervisorType = destinationHypervisorType;
this.destinationStoragePools = destinationStoragePools;
this.conversionTemporaryLocation = conversionTemporaryLocation;
}
public RemoteInstanceTO getSourceInstance() {
return sourceInstance;
}
public Hypervisor.HypervisorType getDestinationHypervisorType() {
return destinationHypervisorType;
}
public List<String> getDestinationStoragePools() {
return destinationStoragePools;
}
public DataStoreTO getConversionTemporaryLocation() {
return conversionTemporaryLocation;
}
@Override
public boolean executeInSequence() {
return false;
}
}

2
debian/control vendored
View File

@ -24,7 +24,7 @@ Description: CloudStack server library
Package: cloudstack-agent
Architecture: all
Depends: ${python:Depends}, ${python3:Depends}, openjdk-11-jre-headless | java11-runtime-headless | java11-runtime | openjdk-11-jre-headless | zulu-11, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, lsb-release, aria2, ufw, apparmor
Depends: ${python:Depends}, ${python3:Depends}, openjdk-11-jre-headless | java11-runtime-headless | java11-runtime | openjdk-11-jre-headless | zulu-11, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, lsb-release, ufw, apparmor
Recommends: init-system-helpers
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
Description: CloudStack agent

View File

@ -96,6 +96,14 @@ public interface StorageManager extends StorageService {
true,
ConfigKey.Scope.Global,
null);
ConfigKey<Integer> ConvertVmwareInstanceToKvmTimeout = new ConfigKey<>(Integer.class,
"convert.vmware.instance.to.kvm.timeout",
"Storage",
"8",
"Timeout (in hours) for the instance conversion process from VMware through the virt-v2v binary on a KVM host",
true,
ConfigKey.Scope.Global,
null);
ConfigKey<Boolean> KvmAutoConvergence = new ConfigKey<>(Boolean.class,
"kvm.auto.convergence",
"Storage",

View File

@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -4588,18 +4587,12 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
@Override
public NicVO doInTransaction(TransactionStatus status) {
NicVO existingNic = _nicDao.findByNetworkIdAndMacAddress(network.getId(), macAddress);
String macAddressToPersist = macAddress;
if (existingNic != null) {
if (!forced) {
throw new CloudRuntimeException("NIC with MAC address = " + macAddress + " exists on network with ID = " + network.getId() +
" and forced flag is disabled");
}
s_logger.debug("Removing existing NIC with MAC address = " + macAddress + " on network with ID = " + network.getId());
existingNic.setState(Nic.State.Deallocating);
existingNic.setRemoved(new Date());
_nicDao.update(existingNic.getId(), existingNic);
macAddressToPersist = generateNewMacAddressIfForced(network, macAddress, forced);
}
NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType());
vo.setMacAddress(macAddress);
vo.setMacAddress(macAddressToPersist);
vo.setAddressFormat(Networks.AddressFormat.Ip4);
if (NetUtils.isValidIp4(finalGuestIp) && StringUtils.isNotEmpty(network.getGateway())) {
vo.setIPv4Address(finalGuestIp);
@ -4643,6 +4636,23 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
return new Pair<NicProfile, Integer>(vmNic, Integer.valueOf(deviceId));
}
private String generateNewMacAddressIfForced(Network network, String macAddress, boolean forced) {
if (!forced) {
throw new CloudRuntimeException("NIC with MAC address = " + macAddress + " exists on network with ID = " + network.getId() +
" and forced flag is disabled");
}
try {
s_logger.debug(String.format("Generating a new mac address on network %s as the mac address %s already exists", network.getName(), macAddress));
String newMacAddress = _networkModel.getNextAvailableMacAddressInNetwork(network.getId());
s_logger.debug(String.format("Successfully generated the mac address %s, using it instead of the conflicting address %s", newMacAddress, macAddress));
return newMacAddress;
} catch (InsufficientAddressCapacityException e) {
String msg = String.format("Could not generate a new mac address on network %s", network.getName());
s_logger.error(msg);
throw new CloudRuntimeException(msg);
}
}
@Override
public void unmanageNics(VirtualMachineProfile vm) {
if (s_logger.isDebugEnabled()) {

View File

@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor.vmware;
package com.cloud.dc;
import java.util.UUID;

View File

@ -15,11 +15,11 @@
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor.vmware.dao;
package com.cloud.dc.dao;
import java.util.List;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.utils.db.GenericDao;
public interface VmwareDatacenterDao extends GenericDao<VmwareDatacenterVO, Long> {

View File

@ -20,10 +20,11 @@ package com.cloud.hypervisor.vmware.dao;
import java.util.List;
import com.cloud.dc.dao.VmwareDatacenterDao;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;

View File

@ -281,6 +281,7 @@
<bean id="publicIpQuarantineDaoImpl" class="com.cloud.network.dao.PublicIpQuarantineDaoImpl" />
<bean id="VMScheduleDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduleDaoImpl" />
<bean id="VMScheduledJobDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDaoImpl" />
<bean id="VmwareDatacenterDaoImpl" class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterDaoImpl" />
<bean id="vnfTemplateDetailsDaoImpl" class="com.cloud.storage.dao.VnfTemplateDetailsDaoImpl" />
<bean id="vnfTemplateNicDaoImpl" class="com.cloud.storage.dao.VnfTemplateNicDaoImpl" />
<bean id="ClusterDrsPlanDaoImpl" class="org.apache.cloudstack.cluster.dao.ClusterDrsPlanDaoImpl" />

View File

@ -35,9 +35,9 @@ import org.springframework.stereotype.Component;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
import com.cloud.hypervisor.vmware.mo.VirtualMachineDiskInfoBuilder;
import com.cloud.storage.Storage.StoragePoolType;

View File

@ -41,9 +41,9 @@ import org.apache.commons.lang3.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.vmware.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
import com.cloud.utils.Pair;
import com.cloud.utils.component.AdapterBase;

View File

@ -743,6 +743,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
protected StorageSubsystemCommandHandler storageHandler;
private boolean convertInstanceVerboseMode = false;
protected boolean dpdkSupport = false;
protected String dpdkOvsPath;
protected String directDownloadTemporaryDownloadPath;
@ -803,6 +804,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return networkDirectDevice;
}
public boolean isConvertInstanceVerboseModeEnabled() {
return convertInstanceVerboseMode;
}
/**
* Defines resource's public and private network interface according to what is configured in agent.properties.
*/
@ -991,6 +996,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
params.putAll(getDeveloperProperties());
}
convertInstanceVerboseMode = BooleanUtils.isTrue(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VIRTV2V_VERBOSE_ENABLED));
pool = (String)params.get("pool");
if (pool == null) {
pool = "/root";

View File

@ -609,7 +609,7 @@ public class LibvirtVMDef {
}
}
enum DiskType {
public enum DiskType {
FILE("file"), BLOCK("block"), DIRECTROY("dir"), NETWORK("network");
String _diskType;
@ -2113,14 +2113,14 @@ public class LibvirtVMDef {
private String path = "/dev/random";
private RngModel rngModel = RngModel.VIRTIO;
private RngBackendModel rngBackendModel = RngBackendModel.RANDOM;
private int rngRateBytes = 2048;
private int rngRatePeriod = 1000;
private Integer rngRateBytes = 2048;
private Integer rngRatePeriod = 1000;
public RngDef(String path) {
this.path = path;
}
public RngDef(String path, int rngRateBytes, int rngRatePeriod) {
public RngDef(String path, Integer rngRateBytes, Integer rngRatePeriod) {
this.path = path;
this.rngRateBytes = rngRateBytes;
this.rngRatePeriod = rngRatePeriod;
@ -2139,7 +2139,7 @@ public class LibvirtVMDef {
this.rngBackendModel = rngBackendModel;
}
public RngDef(String path, RngBackendModel rngBackendModel, int rngRateBytes, int rngRatePeriod) {
public RngDef(String path, RngBackendModel rngBackendModel, Integer rngRateBytes, Integer rngRatePeriod) {
this.path = path;
this.rngBackendModel = rngBackendModel;
this.rngRateBytes = rngRateBytes;
@ -2164,11 +2164,11 @@ public class LibvirtVMDef {
}
public int getRngRateBytes() {
return rngRateBytes;
return rngRateBytes != null ? rngRateBytes : 0;
}
public int getRngRatePeriod() {
return rngRatePeriod;
return rngRatePeriod != null ? rngRatePeriod : 0;
}
@Override

View File

@ -0,0 +1,400 @@
//
// 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.ConvertInstanceAnswer;
import com.cloud.agent.api.ConvertInstanceCommand;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.RemoteInstanceTO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@ResourceWrapper(handles = ConvertInstanceCommand.class)
public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<ConvertInstanceCommand, Answer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtConvertInstanceCommandWrapper.class);
private static final List<Hypervisor.HypervisorType> supportedInstanceConvertSourceHypervisors =
List.of(Hypervisor.HypervisorType.VMware);
protected static final String checkIfConversionIsSupportedCommand = "which virt-v2v";
@Override
public Answer execute(ConvertInstanceCommand cmd, LibvirtComputingResource serverResource) {
RemoteInstanceTO sourceInstance = cmd.getSourceInstance();
Hypervisor.HypervisorType sourceHypervisorType = sourceInstance.getHypervisorType();
String sourceInstanceName = sourceInstance.getInstanceName();
Hypervisor.HypervisorType destinationHypervisorType = cmd.getDestinationHypervisorType();
List<String> destinationStoragePools = cmd.getDestinationStoragePools();
DataStoreTO conversionTemporaryLocation = cmd.getConversionTemporaryLocation();
long timeout = (long) cmd.getWait() * 1000;
if (!isInstanceConversionSupportedOnHost()) {
String msg = String.format("Cannot convert the instance %s from VMware as the virt-v2v binary is not found. " +
"Please install virt-v2v on the host before attempting the instance conversion", sourceInstanceName);
s_logger.info(msg);
return new ConvertInstanceAnswer(cmd, false, msg);
}
if (!areSourceAndDestinationHypervisorsSupported(sourceHypervisorType, destinationHypervisorType)) {
String err = destinationHypervisorType != Hypervisor.HypervisorType.KVM ?
String.format("The destination hypervisor type is %s, KVM was expected, cannot handle it", destinationHypervisorType) :
String.format("The source hypervisor type %s is not supported for KVM conversion", sourceHypervisorType);
s_logger.error(err);
return new ConvertInstanceAnswer(cmd, false, err);
}
final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
KVMStoragePool temporaryStoragePool = getTemporaryStoragePool(conversionTemporaryLocation, storagePoolMgr);
s_logger.info(String.format("Attempting to convert the instance %s from %s to KVM",
sourceInstanceName, sourceHypervisorType));
final String convertInstanceUrl = getConvertInstanceUrl(sourceInstance);
final String temporaryConvertUuid = UUID.randomUUID().toString();
final String temporaryPasswordFilePath = createTemporaryPasswordFileAndRetrievePath(sourceInstance);
final String temporaryConvertPath = temporaryStoragePool.getLocalPath();
boolean verboseModeEnabled = serverResource.isConvertInstanceVerboseModeEnabled();
try {
boolean result = performInstanceConversion(convertInstanceUrl, sourceInstanceName, temporaryPasswordFilePath,
temporaryConvertPath, temporaryConvertUuid, timeout, verboseModeEnabled);
if (!result) {
String err = String.format("The virt-v2v conversion of the instance %s failed. " +
"Please check the agent logs for the virt-v2v output", sourceInstanceName);
s_logger.error(err);
return new ConvertInstanceAnswer(cmd, false, err);
}
String convertedBasePath = String.format("%s/%s", temporaryConvertPath, temporaryConvertUuid);
LibvirtDomainXMLParser xmlParser = parseMigratedVMXmlDomain(convertedBasePath);
List<KVMPhysicalDisk> temporaryDisks = xmlParser == null ?
getTemporaryDisksWithPrefixFromTemporaryPool(temporaryStoragePool, temporaryConvertPath, temporaryConvertUuid) :
getTemporaryDisksFromParsedXml(temporaryStoragePool, xmlParser, convertedBasePath);
List<KVMPhysicalDisk> destinationDisks = moveTemporaryDisksToDestination(temporaryDisks,
destinationStoragePools, storagePoolMgr);
cleanupDisksAndDomainFromTemporaryLocation(temporaryDisks, temporaryStoragePool, temporaryConvertUuid);
UnmanagedInstanceTO convertedInstanceTO = getConvertedUnmanagedInstance(temporaryConvertUuid,
destinationDisks, xmlParser);
return new ConvertInstanceAnswer(cmd, convertedInstanceTO);
} catch (Exception e) {
String error = String.format("Error converting instance %s from %s, due to: %s",
sourceInstanceName, sourceHypervisorType, e.getMessage());
s_logger.error(error, e);
return new ConvertInstanceAnswer(cmd, false, error);
} finally {
s_logger.debug("Cleaning up instance conversion temporary password file");
Script.runSimpleBashScript(String.format("rm -rf %s", temporaryPasswordFilePath));
if (conversionTemporaryLocation instanceof NfsTO) {
s_logger.debug("Cleaning up secondary storage temporary location");
storagePoolMgr.deleteStoragePool(temporaryStoragePool.getType(), temporaryStoragePool.getUuid());
}
}
}
protected KVMStoragePool getTemporaryStoragePool(DataStoreTO conversionTemporaryLocation, KVMStoragePoolManager storagePoolMgr) {
if (conversionTemporaryLocation instanceof NfsTO) {
NfsTO nfsTO = (NfsTO) conversionTemporaryLocation;
return storagePoolMgr.getStoragePoolByURI(nfsTO.getUrl());
} else {
PrimaryDataStoreTO primaryDataStoreTO = (PrimaryDataStoreTO) conversionTemporaryLocation;
return storagePoolMgr.getStoragePool(primaryDataStoreTO.getPoolType(), primaryDataStoreTO.getUuid());
}
}
protected boolean areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType sourceHypervisorType,
Hypervisor.HypervisorType destinationHypervisorType) {
return destinationHypervisorType == Hypervisor.HypervisorType.KVM &&
supportedInstanceConvertSourceHypervisors.contains(sourceHypervisorType);
}
protected List<KVMPhysicalDisk> getTemporaryDisksFromParsedXml(KVMStoragePool pool, LibvirtDomainXMLParser xmlParser, String convertedBasePath) {
List<LibvirtVMDef.DiskDef> disksDefs = xmlParser.getDisks();
disksDefs = disksDefs.stream().filter(x -> x.getDiskType() == LibvirtVMDef.DiskDef.DiskType.FILE &&
x.getDeviceType() == LibvirtVMDef.DiskDef.DeviceType.DISK).collect(Collectors.toList());
if (CollectionUtils.isEmpty(disksDefs)) {
String err = String.format("Cannot find any disk defined on the converted XML domain %s.xml", convertedBasePath);
s_logger.error(err);
throw new CloudRuntimeException(err);
}
sanitizeDisksPath(disksDefs);
return getPhysicalDisksFromDefPaths(disksDefs, pool);
}
private List<KVMPhysicalDisk> getPhysicalDisksFromDefPaths(List<LibvirtVMDef.DiskDef> disksDefs, KVMStoragePool pool) {
List<KVMPhysicalDisk> disks = new ArrayList<>();
for (LibvirtVMDef.DiskDef diskDef : disksDefs) {
KVMPhysicalDisk physicalDisk = pool.getPhysicalDisk(diskDef.getDiskPath());
disks.add(physicalDisk);
}
return disks;
}
protected List<KVMPhysicalDisk> getTemporaryDisksWithPrefixFromTemporaryPool(KVMStoragePool pool, String path, String prefix) {
String msg = String.format("Could not parse correctly the converted XML domain, checking for disks on %s with prefix %s", path, prefix);
s_logger.info(msg);
pool.refresh();
List<KVMPhysicalDisk> disksWithPrefix = pool.listPhysicalDisks()
.stream()
.filter(x -> x.getName().startsWith(prefix) && !x.getName().endsWith(".xml"))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(disksWithPrefix)) {
msg = String.format("Could not find any converted disk with prefix %s on temporary location %s", prefix, path);
s_logger.error(msg);
throw new CloudRuntimeException(msg);
}
return disksWithPrefix;
}
private void cleanupDisksAndDomainFromTemporaryLocation(List<KVMPhysicalDisk> disks,
KVMStoragePool temporaryStoragePool,
String temporaryConvertUuid) {
for (KVMPhysicalDisk disk : disks) {
s_logger.info(String.format("Cleaning up temporary disk %s after conversion from temporary location", disk.getName()));
temporaryStoragePool.deletePhysicalDisk(disk.getName(), Storage.ImageFormat.QCOW2);
}
s_logger.info(String.format("Cleaning up temporary domain %s after conversion from temporary location", temporaryConvertUuid));
Script.runSimpleBashScript(String.format("rm -f %s/%s*.xml", temporaryStoragePool.getLocalPath(), temporaryConvertUuid));
}
protected boolean isInstanceConversionSupportedOnHost() {
int exitValue = Script.runSimpleBashScriptForExitValue(checkIfConversionIsSupportedCommand);
return exitValue == 0;
}
protected void sanitizeDisksPath(List<LibvirtVMDef.DiskDef> disks) {
for (LibvirtVMDef.DiskDef disk : disks) {
String[] diskPathParts = disk.getDiskPath().split("/");
String relativePath = diskPathParts[diskPathParts.length - 1];
disk.setDiskPath(relativePath);
}
}
protected List<KVMPhysicalDisk> moveTemporaryDisksToDestination(List<KVMPhysicalDisk> temporaryDisks,
List<String> destinationStoragePools,
KVMStoragePoolManager storagePoolMgr) {
List<KVMPhysicalDisk> targetDisks = new ArrayList<>();
if (temporaryDisks.size() != destinationStoragePools.size()) {
String warn = String.format("Discrepancy between the converted instance disks (%s) " +
"and the expected number of disks (%s)", temporaryDisks.size(), destinationStoragePools.size());
s_logger.warn(warn);
}
for (int i = 0; i < temporaryDisks.size(); i++) {
String poolPath = destinationStoragePools.get(i);
KVMStoragePool destinationPool = storagePoolMgr.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, poolPath);
if (destinationPool == null) {
String err = String.format("Could not find a storage pool by URI: %s", poolPath);
s_logger.error(err);
continue;
}
KVMPhysicalDisk sourceDisk = temporaryDisks.get(i);
if (s_logger.isDebugEnabled()) {
String msg = String.format("Trying to copy converted instance disk number %s from the temporary location %s" +
" to destination storage pool %s", i, sourceDisk.getPool().getLocalPath(), destinationPool.getUuid());
s_logger.debug(msg);
}
String destinationName = UUID.randomUUID().toString();
KVMPhysicalDisk destinationDisk = storagePoolMgr.copyPhysicalDisk(sourceDisk, destinationName, destinationPool, 7200 * 1000);
targetDisks.add(destinationDisk);
}
return targetDisks;
}
private UnmanagedInstanceTO getConvertedUnmanagedInstance(String baseName,
List<KVMPhysicalDisk> vmDisks,
LibvirtDomainXMLParser xmlParser) {
UnmanagedInstanceTO instanceTO = new UnmanagedInstanceTO();
instanceTO.setName(baseName);
instanceTO.setDisks(getUnmanagedInstanceDisks(vmDisks, xmlParser));
instanceTO.setNics(getUnmanagedInstanceNics(xmlParser));
return instanceTO;
}
private List<UnmanagedInstanceTO.Nic> getUnmanagedInstanceNics(LibvirtDomainXMLParser xmlParser) {
List<UnmanagedInstanceTO.Nic> nics = new ArrayList<>();
if (xmlParser != null) {
List<LibvirtVMDef.InterfaceDef> interfaces = xmlParser.getInterfaces();
for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) {
UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic();
nic.setMacAddress(interfaceDef.getMacAddress());
nic.setNicId(interfaceDef.getBrName());
nic.setAdapterType(interfaceDef.getModel().toString());
nics.add(nic);
}
}
return nics;
}
protected List<UnmanagedInstanceTO.Disk> getUnmanagedInstanceDisks(List<KVMPhysicalDisk> vmDisks, LibvirtDomainXMLParser xmlParser) {
List<UnmanagedInstanceTO.Disk> instanceDisks = new ArrayList<>();
List<LibvirtVMDef.DiskDef> diskDefs = xmlParser != null ? xmlParser.getDisks() : null;
for (int i = 0; i< vmDisks.size(); i++) {
KVMPhysicalDisk physicalDisk = vmDisks.get(i);
KVMStoragePool storagePool = physicalDisk.getPool();
UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk();
disk.setPosition(i);
Pair<String, String> storagePoolHostAndPath = getNfsStoragePoolHostAndPath(storagePool);
disk.setDatastoreHost(storagePoolHostAndPath.first());
disk.setDatastorePath(storagePoolHostAndPath.second());
disk.setDatastoreName(storagePool.getUuid());
disk.setDatastoreType(storagePool.getType().name());
disk.setCapacity(physicalDisk.getVirtualSize());
disk.setFileBaseName(physicalDisk.getName());
if (CollectionUtils.isNotEmpty(diskDefs)) {
LibvirtVMDef.DiskDef diskDef = diskDefs.get(i);
disk.setController(diskDef.getBusType() != null ? diskDef.getBusType().toString() : LibvirtVMDef.DiskDef.DiskBus.VIRTIO.toString());
} else {
// If the job is finished but we cannot parse the XML, the guest VM can use the virtio driver
disk.setController(LibvirtVMDef.DiskDef.DiskBus.VIRTIO.toString());
}
instanceDisks.add(disk);
}
return instanceDisks;
}
protected Pair<String, String> getNfsStoragePoolHostAndPath(KVMStoragePool storagePool) {
String sourceHostIp = null;
String sourcePath = null;
String storagePoolMountPoint = Script.runSimpleBashScript(String.format("mount | grep %s", storagePool.getLocalPath()));
if (StringUtils.isNotEmpty(storagePoolMountPoint)) {
String[] res = storagePoolMountPoint.strip().split(" ");
res = res[0].split(":");
sourceHostIp = res[0].strip();
sourcePath = res[1].strip();
}
return new Pair<>(sourceHostIp, sourcePath);
}
protected boolean performInstanceConversion(String convertInstanceUrl, String sourceInstanceName,
String temporaryPasswordFilePath,
String temporaryConvertFolder,
String temporaryConvertUuid,
long timeout, boolean verboseModeEnabled) {
Script script = new Script("virt-v2v", timeout, s_logger);
script.add("--root", "first");
script.add("-ic", convertInstanceUrl);
script.add(sourceInstanceName);
script.add("--password-file", temporaryPasswordFilePath);
script.add("-o", "local");
script.add("-os", temporaryConvertFolder);
script.add("-of", "qcow2");
script.add("-on", temporaryConvertUuid);
if (verboseModeEnabled) {
script.add("-v");
}
String logPrefix = String.format("virt-v2v source: %s %s progress", convertInstanceUrl, sourceInstanceName);
OutputInterpreter.LineByLineOutputLogger outputLogger = new OutputInterpreter.LineByLineOutputLogger(s_logger, logPrefix);
script.execute(outputLogger);
int exitValue = script.getExitValue();
return exitValue == 0;
}
private String createTemporaryPasswordFileAndRetrievePath(RemoteInstanceTO sourceInstance) {
String password = null;
if (sourceInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
password = sourceInstance.getVcenterPassword();
}
String passwordFile = String.format("/tmp/vmw-%s", UUID.randomUUID());
String msg = String.format("Creating a temporary password file for VMware instance %s conversion on: %s", sourceInstance.getInstanceName(), passwordFile);
s_logger.debug(msg);
Script.runSimpleBashScriptForExitValueAvoidLogging(String.format("echo \"%s\" > %s", password, passwordFile));
return passwordFile;
}
private String getConvertInstanceUrl(RemoteInstanceTO sourceInstance) {
String url = null;
if (sourceInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
url = getConvertInstanceUrlFromVmware(sourceInstance);
}
return url;
}
private String getConvertInstanceUrlFromVmware(RemoteInstanceTO vmwareInstance) {
String vcenter = vmwareInstance.getVcenterHost();
String datacenter = vmwareInstance.getDatacenterName();
String username = vmwareInstance.getVcenterUsername();
String host = vmwareInstance.getHostName();
String cluster = vmwareInstance.getClusterName();
String encodedUsername = encodeUsername(username);
return String.format("vpx://%s@%s/%s/%s/%s?no_verify=1",
encodedUsername, vcenter, datacenter, cluster, host);
}
protected LibvirtDomainXMLParser parseMigratedVMXmlDomain(String installPath) throws IOException {
String xmlPath = String.format("%s.xml", installPath);
if (!new File(xmlPath).exists()) {
String err = String.format("Conversion failed. Unable to find the converted XML domain, expected %s", xmlPath);
s_logger.error(err);
throw new CloudRuntimeException(err);
}
InputStream is = new BufferedInputStream(new FileInputStream(xmlPath));
String xml = IOUtils.toString(is, Charset.defaultCharset());
final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
try {
parser.parseDomainXML(xml);
return parser;
} catch (RuntimeException e) {
String err = String.format("Error parsing the converted instance XML domain at %s: %s", xmlPath, e.getMessage());
s_logger.error(err, e);
s_logger.debug(xml);
return null;
}
}
protected String encodeUsername(String username) {
return URLEncoder.encode(username, Charset.defaultCharset());
}
}

View File

@ -0,0 +1,310 @@
//
// 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.ConvertInstanceCommand;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.RemoteInstanceTO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.storage.Storage;
import com.cloud.utils.Pair;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtConvertInstanceCommandWrapperTest {
@Spy
private LibvirtConvertInstanceCommandWrapper convertInstanceCommandWrapper = Mockito.spy(LibvirtConvertInstanceCommandWrapper.class);
@Mock
private LibvirtComputingResource libvirtComputingResourceMock;
@Mock
private KVMStoragePool temporaryPool;
@Mock
private KVMStoragePool destinationPool;
@Mock
private PrimaryDataStoreTO primaryDataStore;
@Mock
private NfsTO secondaryDataStore;
@Mock
private KVMStoragePoolManager storagePoolManager;
private static final String secondaryPoolUrl = "nfs://192.168.1.1/secondary";
private static final String vmName = "VmToImport";
private static final String hostName = "VmwareHost1";
private static final String vmwareVcenter = "192.168.1.2";
private static final String vmwareDatacenter = "Datacenter";
private static final String vmwareCluster = "Cluster";
private static final String vmwareUsername = "administrator@vsphere.local";
private static final String vmwarePassword = "password";
@Before
public void setUp() {
Mockito.when(secondaryDataStore.getUrl()).thenReturn(secondaryPoolUrl);
Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolManager);
Mockito.when(storagePoolManager.getStoragePoolByURI(secondaryPoolUrl)).thenReturn(temporaryPool);
KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class);
KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2));
}
@Test
public void testIsInstanceConversionSupportedOnHost() {
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(0);
boolean supported = convertInstanceCommandWrapper.isInstanceConversionSupportedOnHost();
Assert.assertTrue(supported);
}
}
@Test
public void testAreSourceAndDestinationHypervisorsSupported() {
boolean supported = convertInstanceCommandWrapper.areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM);
Assert.assertTrue(supported);
}
@Test
public void testAreSourceAndDestinationHypervisorsSupportedUnsupportedSource() {
boolean supported = convertInstanceCommandWrapper.areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType.XenServer, Hypervisor.HypervisorType.KVM);
Assert.assertFalse(supported);
}
@Test
public void testAreSourceAndDestinationHypervisorsSupportedUnsupportedDestination() {
boolean supported = convertInstanceCommandWrapper.areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.VMware);
Assert.assertFalse(supported);
}
@Test
public void testGetTemporaryStoragePool() {
KVMStoragePool temporaryStoragePool = convertInstanceCommandWrapper.getTemporaryStoragePool(secondaryDataStore, libvirtComputingResourceMock.getStoragePoolMgr());
Assert.assertNotNull(temporaryStoragePool);
}
@Test
public void testGetTemporaryDisksWithPrefixFromTemporaryPool() {
String convertPath = "/xyz";
String convertPrefix = UUID.randomUUID().toString();
KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(physicalDisk1.getName()).thenReturn("disk1");
KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(physicalDisk2.getName()).thenReturn("disk2");
KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(convertedDisk1.getName()).thenReturn(String.format("%s-sda", convertPrefix));
KVMPhysicalDisk convertedDisk2 = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(convertedDisk2.getName()).thenReturn(String.format("%s-sdb", convertPrefix));
KVMPhysicalDisk convertedXml = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(convertedXml.getName()).thenReturn(String.format("%s.xml", convertPrefix));
Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2,
convertedDisk1, convertedDisk2, convertedXml));
List<KVMPhysicalDisk> convertedDisks = convertInstanceCommandWrapper.getTemporaryDisksWithPrefixFromTemporaryPool(temporaryPool, convertPath, convertPrefix);
Assert.assertEquals(2, convertedDisks.size());
}
@Test
public void testGetTemporaryDisksFromParsedXml() {
String relativePath = UUID.randomUUID().toString();
String fullPath = String.format("/mnt/xyz/%s", relativePath);
LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef();
LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO;
LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2;
diskDef.defFileBasedDisk(fullPath, "test", bus, type);
LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class);
Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef));
KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(convertedDisk1.getName()).thenReturn("disk1");
Mockito.when(temporaryPool.getPhysicalDisk(relativePath)).thenReturn(convertedDisk1);
List<KVMPhysicalDisk> disks = convertInstanceCommandWrapper.getTemporaryDisksFromParsedXml(temporaryPool, parser, "");
Mockito.verify(convertInstanceCommandWrapper).sanitizeDisksPath(List.of(diskDef));
Assert.assertEquals(1, disks.size());
Assert.assertEquals("disk1", disks.get(0).getName());
}
@Test
public void testSanitizeDisksPath() {
String relativePath = UUID.randomUUID().toString();
String fullPath = String.format("/mnt/xyz/%s", relativePath);
LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef();
LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO;
LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2;
diskDef.defFileBasedDisk(fullPath, "test", bus, type);
convertInstanceCommandWrapper.sanitizeDisksPath(List.of(diskDef));
Assert.assertEquals(relativePath, diskDef.getDiskPath());
}
@Test
public void testMoveTemporaryDisksToDestination() {
KVMPhysicalDisk sourceDisk = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(sourceDisk.getPool()).thenReturn(temporaryPool);
List<KVMPhysicalDisk> disks = List.of(sourceDisk);
String destinationPoolUuid = UUID.randomUUID().toString();
List<String> destinationPools = List.of(destinationPoolUuid);
KVMPhysicalDisk destDisk = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(destDisk.getPath()).thenReturn("xyz");
Mockito.when(storagePoolManager.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, destinationPoolUuid))
.thenReturn(destinationPool);
Mockito.when(storagePoolManager.copyPhysicalDisk(Mockito.eq(sourceDisk), Mockito.anyString(), Mockito.eq(destinationPool), Mockito.anyInt()))
.thenReturn(destDisk);
List<KVMPhysicalDisk> movedDisks = convertInstanceCommandWrapper.moveTemporaryDisksToDestination(disks, destinationPools, storagePoolManager);
Assert.assertEquals(1, movedDisks.size());
Assert.assertEquals("xyz", movedDisks.get(0).getPath());
}
@Test
public void testGetUnmanagedInstanceDisks() {
String relativePath = UUID.randomUUID().toString();
LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef();
LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.IDE;
LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2;
diskDef.defFileBasedDisk(relativePath, relativePath, bus, type);
KVMPhysicalDisk sourceDisk = Mockito.mock(KVMPhysicalDisk.class);
Mockito.when(sourceDisk.getName()).thenReturn(UUID.randomUUID().toString());
Mockito.when(sourceDisk.getPool()).thenReturn(destinationPool);
Mockito.when(destinationPool.getType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
List<KVMPhysicalDisk> disks = List.of(sourceDisk);
LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class);
Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef));
List<UnmanagedInstanceTO.Disk> unmanagedInstanceDisks = convertInstanceCommandWrapper.getUnmanagedInstanceDisks(disks, parser);
Assert.assertEquals(1, unmanagedInstanceDisks.size());
UnmanagedInstanceTO.Disk disk = unmanagedInstanceDisks.get(0);
Assert.assertEquals(LibvirtVMDef.DiskDef.DiskBus.IDE.toString(), disk.getController());
}
@Test
public void testGetNfsStoragePoolHostAndPath() {
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
String localMountPoint = "/mnt/xyz";
String host = "192.168.1.2";
String path = "/secondary";
String mountOutput = String.format("%s:%s on %s type nfs (...)", host, path, localMountPoint);
Mockito.when(temporaryPool.getLocalPath()).thenReturn(localMountPoint);
Mockito.when(Script.runSimpleBashScript(Mockito.anyString()))
.thenReturn(mountOutput);
Pair<String, String> pair = convertInstanceCommandWrapper.getNfsStoragePoolHostAndPath(temporaryPool);
Assert.assertEquals(host, pair.first());
Assert.assertEquals(path, pair.second());
}
}
private RemoteInstanceTO getRemoteInstanceTO(Hypervisor.HypervisorType hypervisorType) {
RemoteInstanceTO remoteInstanceTO = Mockito.mock(RemoteInstanceTO.class);
Mockito.when(remoteInstanceTO.getHypervisorType()).thenReturn(hypervisorType);
Mockito.when(remoteInstanceTO.getInstanceName()).thenReturn(vmName);
Mockito.when(remoteInstanceTO.getHostName()).thenReturn(hostName);
Mockito.when(remoteInstanceTO.getVcenterHost()).thenReturn(vmwareVcenter);
Mockito.when(remoteInstanceTO.getDatacenterName()).thenReturn(vmwareDatacenter);
Mockito.when(remoteInstanceTO.getClusterName()).thenReturn(vmwareCluster);
Mockito.when(remoteInstanceTO.getVcenterUsername()).thenReturn(vmwareUsername);
Mockito.when(remoteInstanceTO.getVcenterPassword()).thenReturn(vmwarePassword);
return remoteInstanceTO;
}
private ConvertInstanceCommand getConvertInstanceCommand(RemoteInstanceTO remoteInstanceTO, Hypervisor.HypervisorType hypervisorType) {
ConvertInstanceCommand cmd = Mockito.mock(ConvertInstanceCommand.class);
Mockito.when(cmd.getSourceInstance()).thenReturn(remoteInstanceTO);
Mockito.when(cmd.getDestinationHypervisorType()).thenReturn(hypervisorType);
Mockito.when(cmd.getWait()).thenReturn(14400);
Mockito.when(cmd.getConversionTemporaryLocation()).thenReturn(secondaryDataStore);
return cmd;
}
@Test
public void testExecuteConvertUnsupportedOnTheHost() {
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
RemoteInstanceTO remoteInstanceTO = getRemoteInstanceTO(Hypervisor.HypervisorType.VMware);
ConvertInstanceCommand cmd = getConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM);
Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(1);
Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
Assert.assertFalse(answer.getResult());
}
}
@Test
public void testExecuteConvertUnsupportedHypervisors() {
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
RemoteInstanceTO remoteInstanceTO = getRemoteInstanceTO(Hypervisor.HypervisorType.XenServer);
ConvertInstanceCommand cmd = getConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM);
Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(0);
Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
Assert.assertFalse(answer.getResult());
}
}
@Test
public void testExecuteConvertFailure() {
RemoteInstanceTO remoteInstanceTO = getRemoteInstanceTO(Hypervisor.HypervisorType.VMware);
ConvertInstanceCommand cmd = getConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM);
String localMountPoint = "/mnt/xyz";
Mockito.when(temporaryPool.getLocalPath()).thenReturn(localMountPoint);
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class);
MockedConstruction<Script> ignored2 = Mockito.mockConstruction(Script.class, (mock, context) -> {
Mockito.when(mock.execute()).thenReturn("");
Mockito.when(mock.getExitValue()).thenReturn(1);
})
) {
Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(0);
Mockito.when(Script.runSimpleBashScriptForExitValueAvoidLogging(Mockito.anyString())).thenReturn(0);
Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn("");
Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
Assert.assertFalse(answer.getResult());
Mockito.verify(convertInstanceCommandWrapper).performInstanceConversion(Mockito.anyString(),
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean());
}
}
}

View File

@ -27,6 +27,12 @@ import java.util.UUID;
import javax.inject.Inject;
import com.cloud.hypervisor.vmware.mo.DatastoreMO;
import com.cloud.hypervisor.vmware.mo.HostMO;
import com.cloud.hypervisor.vmware.util.VmwareClient;
import com.cloud.hypervisor.vmware.util.VmwareHelper;
import com.cloud.vm.VmDetailConstants;
import com.vmware.vim25.VirtualMachinePowerState;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
@ -44,6 +50,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -80,9 +87,9 @@ import com.cloud.host.dao.HostDetailsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.HypervisorGuruBase;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
import com.cloud.hypervisor.vmware.manager.VmwareManager;
import com.cloud.hypervisor.vmware.mo.DatacenterMO;
@ -1217,4 +1224,118 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) {
return super.toVirtualMachineTO(vmProfile);
}
private VmwareContext connectToVcenter(String vcenter, String username, String password) throws Exception {
VmwareClient vimClient = new VmwareClient(vcenter);
String serviceUrl = "https://" + vcenter + "/sdk/vimService";
vimClient.connect(serviceUrl, username, password);
return new VmwareContext(vimClient, vcenter);
}
private void relocateClonedVMToSourceHost(VirtualMachineMO clonedVM, HostMO sourceHost) throws Exception {
if (!clonedVM.getRunningHost().getMor().equals(sourceHost.getMor())) {
s_logger.debug(String.format("Relocating VM to the same host as the source VM: %s", sourceHost.getHostName()));
if (!clonedVM.relocate(sourceHost.getMor())) {
String err = String.format("Cannot relocate cloned VM %s to the source host %s", clonedVM.getVmName(), sourceHost.getHostName());
s_logger.error(err);
throw new CloudRuntimeException(err);
}
}
}
private VirtualMachineMO createCloneFromSourceVM(String vmName, VirtualMachineMO vmMo,
DatacenterMO dataCenterMO) throws Exception {
HostMO sourceHost = vmMo.getRunningHost();
String cloneName = UUID.randomUUID().toString();
DatastoreMO datastoreMO = vmMo.getAllDatastores().get(0); //pick the first datastore
ManagedObjectReference morPool = vmMo.getRunningHost().getHyperHostOwnerResourcePool();
boolean result = vmMo.createFullClone(cloneName, dataCenterMO.getVmFolder(), morPool, datastoreMO.getMor(), Storage.ProvisioningType.THIN);
VirtualMachineMO clonedVM = dataCenterMO.findVm(cloneName);
if (!result || clonedVM == null) {
String err = String.format("Could not clone VM %s before migration from VMware", vmName);
s_logger.error(err);
throw new CloudRuntimeException(err);
}
relocateClonedVMToSourceHost(clonedVM, sourceHost);
return clonedVM;
}
@Override
public UnmanagedInstanceTO cloneHypervisorVMOutOfBand(String hostIp, String vmName,
Map<String, String> params) {
s_logger.debug(String.format("Cloning VM %s on external vCenter %s", vmName, hostIp));
String vcenter = params.get(VmDetailConstants.VMWARE_VCENTER_HOST);
String datacenter = params.get(VmDetailConstants.VMWARE_DATACENTER_NAME);
String username = params.get(VmDetailConstants.VMWARE_VCENTER_USERNAME);
String password = params.get(VmDetailConstants.VMWARE_VCENTER_PASSWORD);
try {
VmwareContext context = connectToVcenter(vcenter, username, password);
DatacenterMO dataCenterMO = new DatacenterMO(context, datacenter);
VirtualMachineMO vmMo = dataCenterMO.findVm(vmName);
if (vmMo == null) {
String err = String.format("Cannot find VM with name %s on %s/%s", vmName, vcenter, datacenter);
s_logger.error(err);
throw new CloudRuntimeException(err);
}
VirtualMachinePowerState sourceVmPowerState = vmMo.getPowerState();
if (sourceVmPowerState == VirtualMachinePowerState.POWERED_ON && isWindowsVm(vmMo)) {
s_logger.debug(String.format("VM %s is a Windows VM and its Running, cannot be imported." +
"Please gracefully shut it down before attempting the import",
vmName));
}
VirtualMachineMO clonedVM = createCloneFromSourceVM(vmName, vmMo, dataCenterMO);
s_logger.debug(String.format("VM %s cloned successfully", vmName));
UnmanagedInstanceTO clonedInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), clonedVM);
setNicsFromSourceVM(clonedInstance, vmMo);
clonedInstance.setCloneSourcePowerState(sourceVmPowerState == VirtualMachinePowerState.POWERED_ON ? UnmanagedInstanceTO.PowerState.PowerOn : UnmanagedInstanceTO.PowerState.PowerOff);
return clonedInstance;
} catch (Exception e) {
String err = String.format("Error cloning VM: %s from external vCenter %s: %s", vmName, vcenter, e.getMessage());
s_logger.error(err, e);
throw new CloudRuntimeException(err, e);
}
}
private boolean isWindowsVm(VirtualMachineMO vmMo) throws Exception {
UnmanagedInstanceTO sourceInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), vmMo);
return sourceInstance.getOperatingSystem().toLowerCase().contains("windows");
}
private void setNicsFromSourceVM(UnmanagedInstanceTO clonedInstance, VirtualMachineMO vmMo) throws Exception {
UnmanagedInstanceTO sourceInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), vmMo);
List<UnmanagedInstanceTO.Disk> sourceDisks = sourceInstance.getDisks();
List<UnmanagedInstanceTO.Disk> clonedDisks = clonedInstance.getDisks();
for (int i = 0; i < sourceDisks.size(); i++) {
UnmanagedInstanceTO.Disk sourceDisk = sourceDisks.get(i);
UnmanagedInstanceTO.Disk clonedDisk = clonedDisks.get(i);
clonedDisk.setDiskId(sourceDisk.getDiskId());
}
}
@Override
public boolean removeClonedHypervisorVMOutOfBand(String hostIp, String vmName, Map<String, String> params) {
s_logger.debug(String.format("Removing VM %s on external vCenter %s", vmName, hostIp));
String vcenter = params.get(VmDetailConstants.VMWARE_VCENTER_HOST);
String datacenter = params.get(VmDetailConstants.VMWARE_DATACENTER_NAME);
String username = params.get(VmDetailConstants.VMWARE_VCENTER_USERNAME);
String password = params.get(VmDetailConstants.VMWARE_VCENTER_PASSWORD);
try {
VmwareContext context = connectToVcenter(vcenter, username, password);
DatacenterMO dataCenterMO = new DatacenterMO(context, datacenter);
VirtualMachineMO vmMo = dataCenterMO.findVm(vmName);
if (vmMo == null) {
String err = String.format("Cannot find VM %s on datacenter %s, not possible to remove VM out of band",
vmName, datacenter);
s_logger.error(err);
return false;
}
return vmMo.destroy();
} catch (Exception e) {
String err = String.format("Error destroying external VM %s: %s", vmName, e.getMessage());
s_logger.error(err, e);
return false;
}
}
}

View File

@ -17,6 +17,8 @@
package com.cloud.hypervisor.vmware;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.dc.VsphereStoragePolicy;
import com.cloud.exception.DiscoveryException;
import com.cloud.exception.ResourceInUseException;
@ -25,11 +27,13 @@ import com.cloud.utils.component.PluggableService;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.command.admin.zone.AddVmwareDcCmd;
import org.apache.cloudstack.api.command.admin.zone.ImportVsphereStoragePoliciesCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcVmsCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcsCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePoliciesCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePolicyCompatiblePoolsCmd;
import org.apache.cloudstack.api.command.admin.zone.RemoveVmwareDcCmd;
import org.apache.cloudstack.api.command.admin.zone.UpdateVmwareDcCmd;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import java.util.List;
@ -48,4 +52,6 @@ public interface VmwareDatacenterService extends PluggableService {
List<? extends VsphereStoragePolicy> listVsphereStoragePolicies(ListVsphereStoragePoliciesCmd cmd);
List<StoragePool> listVsphereStoragePolicyCompatibleStoragePools(ListVsphereStoragePolicyCompatiblePoolsCmd cmd);
List<UnmanagedInstanceTO> listVMsInDatacenter(ListVmwareDcVmsCmd cmd);
}

View File

@ -27,6 +27,7 @@ import java.util.UUID;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.dc.VmwareDatacenterVO;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.log4j.Logger;
@ -46,7 +47,7 @@ import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
import com.cloud.hypervisor.vmware.manager.VmwareManager;
import com.cloud.hypervisor.vmware.mo.ClusterMO;

View File

@ -43,8 +43,10 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import javax.persistence.EntityExistsException;
import com.cloud.hypervisor.vmware.util.VmwareClient;
import org.apache.cloudstack.api.command.admin.zone.AddVmwareDcCmd;
import org.apache.cloudstack.api.command.admin.zone.ImportVsphereStoragePoliciesCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcVmsCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcsCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePoliciesCmd;
import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePolicyCompatiblePoolsCmd;
@ -61,6 +63,7 @@ import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplain
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
@ -109,13 +112,13 @@ import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.hypervisor.vmware.LegacyZoneVO;
import com.cloud.hypervisor.vmware.VmwareCleanupMaid;
import com.cloud.hypervisor.vmware.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.hypervisor.vmware.VmwareDatacenterService;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
import com.cloud.hypervisor.vmware.dao.LegacyZoneDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
import com.cloud.hypervisor.vmware.mo.CustomFieldConstants;
import com.cloud.hypervisor.vmware.mo.DatacenterMO;
@ -1113,6 +1116,7 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw
cmdList.add(ImportVsphereStoragePoliciesCmd.class);
cmdList.add(ListVsphereStoragePoliciesCmd.class);
cmdList.add(ListVsphereStoragePolicyCompatiblePoolsCmd.class);
cmdList.add(ListVmwareDcVmsCmd.class);
return cmdList;
}
@ -1586,6 +1590,62 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw
return compatiblePools;
}
@Override
public List<UnmanagedInstanceTO> listVMsInDatacenter(ListVmwareDcVmsCmd cmd) {
String vcenter = cmd.getVcenter();
String datacenterName = cmd.getDatacenterName();
String username = cmd.getUsername();
String password = cmd.getPassword();
Long existingVcenterId = cmd.getExistingVcenterId();
String keyword = cmd.getKeyword();
if ((existingVcenterId == null && StringUtils.isBlank(vcenter)) ||
(existingVcenterId != null && StringUtils.isNotBlank(vcenter))) {
throw new InvalidParameterValueException("Please provide an existing vCenter ID or a vCenter IP/Name, parameters are mutually exclusive");
}
if (existingVcenterId == null && StringUtils.isAnyBlank(vcenter, datacenterName, username, password)) {
throw new InvalidParameterValueException("Please set all the information for a vCenter IP/Name, datacenter, username and password");
}
if (existingVcenterId != null) {
VmwareDatacenterVO vmwareDc = vmwareDcDao.findById(existingVcenterId);
if (vmwareDc == null) {
throw new InvalidParameterValueException(String.format("Cannot find a VMware datacenter with ID %s", existingVcenterId));
}
vcenter = vmwareDc.getVcenterHost();
datacenterName = vmwareDc.getVmwareDatacenterName();
username = vmwareDc.getUser();
password = vmwareDc.getPassword();
}
try {
s_logger.debug(String.format("Connecting to the VMware datacenter %s at vCenter %s to retrieve VMs",
datacenterName, vcenter));
String serviceUrl = String.format("https://%s/sdk/vimService", vcenter);
VmwareClient vimClient = new VmwareClient(vcenter);
vimClient.connect(serviceUrl, username, password);
VmwareContext context = new VmwareContext(vimClient, vcenter);
DatacenterMO dcMo = new DatacenterMO(context, datacenterName);
ManagedObjectReference dcMor = dcMo.getMor();
if (dcMor == null) {
String msg = String.format("Unable to find VMware datacenter %s in vCenter %s",
datacenterName, vcenter);
s_logger.error(msg);
throw new InvalidParameterValueException(msg);
}
List<UnmanagedInstanceTO> instances = dcMo.getAllVmsOnDatacenter();
return StringUtils.isBlank(keyword) ? instances :
instances.stream().filter(x -> x.getName().toLowerCase().contains(keyword.toLowerCase())).collect(Collectors.toList());
} catch (Exception e) {
String errorMsg = String.format("Error retrieving stopped VMs from the VMware VC %s datacenter %s: %s",
vcenter, datacenterName, e.getMessage());
s_logger.error(errorMsg, e);
throw new CloudRuntimeException(errorMsg);
}
}
@Override
public boolean hasNexusVSM(Long clusterId) {
ClusterVSMMapVO vsmMapVo = null;

View File

@ -242,7 +242,6 @@ import com.cloud.hypervisor.vmware.mo.DatacenterMO;
import com.cloud.hypervisor.vmware.mo.DatastoreFile;
import com.cloud.hypervisor.vmware.mo.DatastoreMO;
import com.cloud.hypervisor.vmware.mo.DiskControllerType;
import com.cloud.hypervisor.vmware.mo.DistributedVirtualSwitchMO;
import com.cloud.hypervisor.vmware.mo.FeatureKeyConstants;
import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO;
import com.cloud.hypervisor.vmware.mo.HostMO;
@ -312,23 +311,19 @@ import com.vmware.vim25.CustomFieldStringValue;
import com.vmware.vim25.DVPortConfigInfo;
import com.vmware.vim25.DVPortConfigSpec;
import com.vmware.vim25.DasVmPriority;
import com.vmware.vim25.DatastoreInfo;
import com.vmware.vim25.DatastoreSummary;
import com.vmware.vim25.DistributedVirtualPort;
import com.vmware.vim25.DistributedVirtualSwitchPortConnection;
import com.vmware.vim25.DistributedVirtualSwitchPortCriteria;
import com.vmware.vim25.DynamicProperty;
import com.vmware.vim25.GuestInfo;
import com.vmware.vim25.GuestNicInfo;
import com.vmware.vim25.GuestOsDescriptor;
import com.vmware.vim25.HostCapability;
import com.vmware.vim25.HostConfigInfo;
import com.vmware.vim25.HostFileSystemMountInfo;
import com.vmware.vim25.HostHostBusAdapter;
import com.vmware.vim25.HostInternetScsiHba;
import com.vmware.vim25.HostPortGroupSpec;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.NasDatastoreInfo;
import com.vmware.vim25.ObjectContent;
import com.vmware.vim25.OptionValue;
import com.vmware.vim25.PerfCounterInfo;
@ -353,14 +348,12 @@ import com.vmware.vim25.VirtualDevice;
import com.vmware.vim25.VirtualDeviceBackingInfo;
import com.vmware.vim25.VirtualDeviceConfigSpec;
import com.vmware.vim25.VirtualDeviceConfigSpecOperation;
import com.vmware.vim25.VirtualDeviceFileBackingInfo;
import com.vmware.vim25.VirtualDisk;
import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
import com.vmware.vim25.VirtualEthernetCard;
import com.vmware.vim25.VirtualEthernetCardDistributedVirtualPortBackingInfo;
import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo;
import com.vmware.vim25.VirtualEthernetCardOpaqueNetworkBackingInfo;
import com.vmware.vim25.VirtualIDEController;
import com.vmware.vim25.VirtualMachineBootOptions;
import com.vmware.vim25.VirtualMachineConfigSpec;
import com.vmware.vim25.VirtualMachineDefinedProfileSpec;
@ -374,14 +367,10 @@ import com.vmware.vim25.VirtualMachineRelocateSpecDiskLocator;
import com.vmware.vim25.VirtualMachineRuntimeInfo;
import com.vmware.vim25.VirtualMachineToolsStatus;
import com.vmware.vim25.VirtualMachineVideoCard;
import com.vmware.vim25.VirtualPCNet32;
import com.vmware.vim25.VirtualSCSIController;
import com.vmware.vim25.VirtualUSBController;
import com.vmware.vim25.VirtualVmxnet2;
import com.vmware.vim25.VirtualVmxnet3;
import com.vmware.vim25.VmConfigInfo;
import com.vmware.vim25.VmConfigSpec;
import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec;
import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec;
public class VmwareResource extends ServerResourceBase implements StoragePoolResource, ServerResource, VmwareHostService, VirtualRouterDeployer {
@ -453,10 +442,6 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
protected static final String s_relativePathSystemVmKeyFileInstallDir = "scripts/vm/systemvm/id_rsa.cloud";
protected static final String s_defaultPathSystemVmKeyFile = "/usr/share/cloudstack-common/scripts/vm/systemvm/id_rsa.cloud";
public Gson getGson() {
return _gson;
}
public VmwareResource() {
_gson = GsonHelper.getGsonLogger();
}
@ -987,7 +972,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
// OfflineVmwareMigration: 5. ignore/replace the rest of the try-block; It is the functional bit
VirtualDisk disk = getDiskAfterResizeDiskValidations(vmMo, path);
String vmdkAbsFile = getAbsoluteVmdkFile(disk);
String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(disk);
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
@ -4729,7 +4714,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
s_logger.debug(String.format("locating disk for volume (%d) using path %s", volumeId, volumePath));
}
Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, volumePath + VMDK_EXTENSION);
String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first());
String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(diskInfo.first());
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
}
@ -4928,7 +4913,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
}
VirtualDisk disk = vdisk.first();
String vmdkAbsFile = getAbsoluteVmdkFile(disk);
String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(disk);
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
}
@ -5048,7 +5033,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
String fullVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(targetDsMo, vmName, volumePath + VMDK_EXTENSION);
Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, appendFileType(volumePath, VMDK_EXTENSION));
String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first());
String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(diskInfo.first());
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
}
@ -7105,16 +7090,6 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
return dcMo.findVm(vol.getPath());
}
public String getAbsoluteVmdkFile(VirtualDisk disk) {
String vmdkAbsFile = null;
VirtualDeviceBackingInfo backingInfo = disk.getBacking();
if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;
vmdkAbsFile = diskBackingInfo.getFileName();
}
return vmdkAbsFile;
}
protected File getSystemVmKeyFile() {
if (s_systemVmKeyFile == null) {
syncFetchSystemVmKeyFile();
@ -7149,255 +7124,6 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
return keyFile;
}
private List<UnmanagedInstanceTO.Disk> getUnmanageInstanceDisks(VirtualMachineMO vmMo) {
List<UnmanagedInstanceTO.Disk> instanceDisks = new ArrayList<>();
VirtualDisk[] disks = null;
try {
disks = vmMo.getAllDiskDevice();
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance disks. " + e.getMessage());
}
if (disks != null) {
for (VirtualDevice diskDevice : disks) {
try {
if (diskDevice instanceof VirtualDisk) {
UnmanagedInstanceTO.Disk instanceDisk = new UnmanagedInstanceTO.Disk();
VirtualDisk disk = (VirtualDisk) diskDevice;
instanceDisk.setDiskId(disk.getDiskObjectId());
instanceDisk.setLabel(disk.getDeviceInfo() != null ? disk.getDeviceInfo().getLabel() : "");
instanceDisk.setFileBaseName(vmMo.getVmdkFileBaseName(disk));
instanceDisk.setImagePath(getAbsoluteVmdkFile(disk));
instanceDisk.setCapacity(disk.getCapacityInBytes());
instanceDisk.setPosition(diskDevice.getUnitNumber());
DatastoreFile file = new DatastoreFile(getAbsoluteVmdkFile(disk));
if (StringUtils.isNoneEmpty(file.getFileBaseName(), file.getDatastoreName())) {
VirtualMachineDiskInfo diskInfo = vmMo.getDiskInfoBuilder().getDiskInfoByBackingFileBaseName(file.getFileBaseName(), file.getDatastoreName());
instanceDisk.setChainInfo(getGson().toJson(diskInfo));
}
for (VirtualDevice device : vmMo.getAllDeviceList()) {
if (diskDevice.getControllerKey() == device.getKey()) {
if (device instanceof VirtualIDEController) {
instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
instanceDisk.setControllerUnit(((VirtualIDEController) device).getBusNumber());
} else if (device instanceof VirtualSCSIController) {
instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
instanceDisk.setControllerUnit(((VirtualSCSIController) device).getBusNumber());
} else {
instanceDisk.setController(DiskControllerType.none.toString());
}
break;
}
}
if (disk.getBacking() instanceof VirtualDeviceFileBackingInfo) {
VirtualDeviceFileBackingInfo diskBacking = (VirtualDeviceFileBackingInfo) disk.getBacking();
ManagedObjectReference morDs = diskBacking.getDatastore();
DatastoreInfo info = (DatastoreInfo)vmMo.getContext().getVimClient().getDynamicProperty(diskBacking.getDatastore(), "info");
if (info instanceof NasDatastoreInfo) {
NasDatastoreInfo dsInfo = (NasDatastoreInfo) info;
instanceDisk.setDatastoreName(dsInfo.getName());
if (dsInfo.getNas() != null) {
instanceDisk.setDatastoreHost(dsInfo.getNas().getRemoteHost());
instanceDisk.setDatastorePath(dsInfo.getNas().getRemotePath());
instanceDisk.setDatastoreType(dsInfo.getNas().getType());
}
} else {
instanceDisk.setDatastoreName(info.getName());
}
}
s_logger.info(vmMo.getName() + " " + disk.getDeviceInfo().getLabel() + " " + disk.getDeviceInfo().getSummary() + " " + disk.getDiskObjectId() + " " + disk.getCapacityInKB() + " " + instanceDisk.getController());
instanceDisks.add(instanceDisk);
}
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance disk info. " + e.getMessage());
}
}
Collections.sort(instanceDisks, new Comparator<UnmanagedInstanceTO.Disk>() {
@Override
public int compare(final UnmanagedInstanceTO.Disk disk1, final UnmanagedInstanceTO.Disk disk2) {
return extractInt(disk1) - extractInt(disk2);
}
int extractInt(UnmanagedInstanceTO.Disk disk) {
String num = disk.getLabel().replaceAll("\\D", "");
// return 0 if no digits found
return num.isEmpty() ? 0 : Integer.parseInt(num);
}
});
}
return instanceDisks;
}
private List<UnmanagedInstanceTO.Nic> getUnmanageInstanceNics(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
List<UnmanagedInstanceTO.Nic> instanceNics = new ArrayList<>();
HashMap<String, List<String>> guestNicMacIPAddressMap = new HashMap<>();
try {
GuestInfo guestInfo = vmMo.getGuestInfo();
if (guestInfo.getToolsStatus() == VirtualMachineToolsStatus.TOOLS_OK) {
for (GuestNicInfo nicInfo: guestInfo.getNet()) {
if (CollectionUtils.isNotEmpty(nicInfo.getIpAddress())) {
List<String> ipAddresses = new ArrayList<>();
for (String ipAddress : nicInfo.getIpAddress()) {
if (NetUtils.isValidIp4(ipAddress)) {
ipAddresses.add(ipAddress);
}
}
guestNicMacIPAddressMap.put(nicInfo.getMacAddress(), ipAddresses);
}
}
} else {
s_logger.info(String.format("Unable to retrieve guest nics for instance: %s from VMware tools as tools status: %s", vmMo.getName(), guestInfo.getToolsStatus().toString()));
}
} catch (Exception e) {
s_logger.info("Unable to retrieve guest nics for instance from VMware tools. " + e.getMessage());
}
VirtualDevice[] nics = null;
try {
nics = vmMo.getNicDevices();
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance nics. " + e.getMessage());
}
if (nics != null) {
for (VirtualDevice nic : nics) {
try {
VirtualEthernetCard ethCardDevice = (VirtualEthernetCard) nic;
s_logger.error(nic.getClass().getCanonicalName() + " " + nic.getBacking().getClass().getCanonicalName() + " " + ethCardDevice.getMacAddress());
UnmanagedInstanceTO.Nic instanceNic = new UnmanagedInstanceTO.Nic();
instanceNic.setNicId(ethCardDevice.getDeviceInfo().getLabel());
if (ethCardDevice instanceof VirtualPCNet32) {
instanceNic.setAdapterType(VirtualEthernetCardType.PCNet32.toString());
} else if (ethCardDevice instanceof VirtualVmxnet2) {
instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet2.toString());
} else if (ethCardDevice instanceof VirtualVmxnet3) {
instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet3.toString());
} else {
instanceNic.setAdapterType(VirtualEthernetCardType.E1000.toString());
}
instanceNic.setMacAddress(ethCardDevice.getMacAddress());
if (guestNicMacIPAddressMap.containsKey(instanceNic.getMacAddress())) {
instanceNic.setIpAddress(guestNicMacIPAddressMap.get(instanceNic.getMacAddress()));
}
if (ethCardDevice.getSlotInfo() != null) {
instanceNic.setPciSlot(ethCardDevice.getSlotInfo().toString());
}
VirtualDeviceBackingInfo backing = ethCardDevice.getBacking();
if (backing instanceof VirtualEthernetCardDistributedVirtualPortBackingInfo) {
VirtualEthernetCardDistributedVirtualPortBackingInfo backingInfo = (VirtualEthernetCardDistributedVirtualPortBackingInfo) backing;
DistributedVirtualSwitchPortConnection port = backingInfo.getPort();
String portKey = port.getPortKey();
String portGroupKey = port.getPortgroupKey();
String dvSwitchUuid = port.getSwitchUuid();
s_logger.debug("NIC " + nic.toString() + " is connected to dvSwitch " + dvSwitchUuid + " pg " + portGroupKey + " port " + portKey);
ManagedObjectReference dvSwitchManager = vmMo.getContext().getVimClient().getServiceContent().getDvSwitchManager();
ManagedObjectReference dvSwitch = vmMo.getContext().getVimClient().getService().queryDvsByUuid(dvSwitchManager, dvSwitchUuid);
// Get all ports
DistributedVirtualSwitchPortCriteria criteria = new DistributedVirtualSwitchPortCriteria();
criteria.setInside(true);
criteria.getPortgroupKey().add(portGroupKey);
List<DistributedVirtualPort> dvPorts = vmMo.getContext().getVimClient().getService().fetchDVPorts(dvSwitch, criteria);
for (DistributedVirtualPort dvPort : dvPorts) {
// Find the port for this NIC by portkey
if (portKey.equals(dvPort.getKey())) {
VMwareDVSPortSetting settings = (VMwareDVSPortSetting) dvPort.getConfig().getSetting();
if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchVlanIdSpec) {
VmwareDistributedVirtualSwitchVlanIdSpec vlanId = (VmwareDistributedVirtualSwitchVlanIdSpec) settings.getVlan();
s_logger.trace("Found port " + dvPort.getKey() + " with vlan " + vlanId.getVlanId());
if (vlanId.getVlanId() > 0 && vlanId.getVlanId() < 4095) {
instanceNic.setVlan(vlanId.getVlanId());
}
} else if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchPvlanSpec) {
VmwareDistributedVirtualSwitchPvlanSpec pvlanSpec = (VmwareDistributedVirtualSwitchPvlanSpec) settings.getVlan();
s_logger.trace("Found port " + dvPort.getKey() + " with pvlan " + pvlanSpec.getPvlanId());
if (pvlanSpec.getPvlanId() > 0 && pvlanSpec.getPvlanId() < 4095) {
DistributedVirtualSwitchMO dvSwitchMo = new DistributedVirtualSwitchMO(vmMo.getContext(), dvSwitch);
Pair<Integer, HypervisorHostHelper.PvlanType> vlanDetails = dvSwitchMo.retrieveVlanFromPvlan(pvlanSpec.getPvlanId(), dvSwitch);
if (vlanDetails != null && vlanDetails.first() != null && vlanDetails.second() != null) {
instanceNic.setVlan(vlanDetails.first());
instanceNic.setPvlan(pvlanSpec.getPvlanId());
instanceNic.setPvlanType(vlanDetails.second().toString());
}
}
}
break;
}
}
} else if (backing instanceof VirtualEthernetCardNetworkBackingInfo) {
VirtualEthernetCardNetworkBackingInfo backingInfo = (VirtualEthernetCardNetworkBackingInfo) backing;
instanceNic.setNetwork(backingInfo.getDeviceName());
if (hyperHost instanceof HostMO) {
HostMO hostMo = (HostMO) hyperHost;
HostPortGroupSpec portGroupSpec = hostMo.getHostPortGroupSpec(backingInfo.getDeviceName());
instanceNic.setVlan(portGroupSpec.getVlanId());
}
}
instanceNics.add(instanceNic);
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance nic info. " + e.getMessage());
}
}
Collections.sort(instanceNics, new Comparator<UnmanagedInstanceTO.Nic>() {
@Override
public int compare(final UnmanagedInstanceTO.Nic nic1, final UnmanagedInstanceTO.Nic nic2) {
return extractInt(nic1) - extractInt(nic2);
}
int extractInt(UnmanagedInstanceTO.Nic nic) {
String num = nic.getNicId().replaceAll("\\D", "");
// return 0 if no digits found
return num.isEmpty() ? 0 : Integer.parseInt(num);
}
});
}
return instanceNics;
}
private UnmanagedInstanceTO getUnmanagedInstance(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
UnmanagedInstanceTO instance = null;
try {
instance = new UnmanagedInstanceTO();
instance.setName(vmMo.getVmName());
instance.setInternalCSName(vmMo.getInternalCSName());
instance.setCpuCores(vmMo.getConfigSummary().getNumCpu());
instance.setCpuCoresPerSocket(vmMo.getCoresPerSocket());
instance.setCpuSpeed(vmMo.getConfigSummary().getCpuReservation());
instance.setMemory(vmMo.getConfigSummary().getMemorySizeMB());
instance.setOperatingSystemId(vmMo.getVmGuestInfo().getGuestId());
if (StringUtils.isEmpty(instance.getOperatingSystemId())) {
instance.setOperatingSystemId(vmMo.getConfigSummary().getGuestId());
}
VirtualMachineGuestOsIdentifier osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST;
try {
osIdentifier = VirtualMachineGuestOsIdentifier.fromValue(instance.getOperatingSystemId());
} catch (IllegalArgumentException iae) {
if (StringUtils.isNotEmpty(instance.getOperatingSystemId()) && instance.getOperatingSystemId().contains("64")) {
osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST_64;
}
}
instance.setOperatingSystem(vmMo.getGuestInfo().getGuestFullName());
if (StringUtils.isEmpty(instance.getOperatingSystem())) {
instance.setOperatingSystem(vmMo.getConfigSummary().getGuestFullName());
}
UnmanagedInstanceTO.PowerState powerState = UnmanagedInstanceTO.PowerState.PowerUnknown;
if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_ON")) {
powerState = UnmanagedInstanceTO.PowerState.PowerOn;
}
if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_OFF")) {
powerState = UnmanagedInstanceTO.PowerState.PowerOff;
}
instance.setPowerState(powerState);
instance.setDisks(getUnmanageInstanceDisks(vmMo));
instance.setNics(getUnmanageInstanceNics(hyperHost, vmMo));
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance info. " + e.getMessage());
}
return instance;
}
private Answer execute(GetUnmanagedInstancesCommand cmd) {
VmwareContext context = getServiceContext();
HashMap<String, UnmanagedInstanceTO> unmanagedInstances = new HashMap<>();
@ -7426,7 +7152,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
!cmd.getInstanceName().equals(vmMo.getVmName())) {
continue;
}
UnmanagedInstanceTO instance = getUnmanagedInstance(hyperHost, vmMo);
UnmanagedInstanceTO instance = VmwareHelper.getUnmanagedInstance(hyperHost, vmMo);
if (instance != null) {
unmanagedInstances.put(instance.getName(), instance);
}
@ -7555,7 +7281,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
VirtualMachineRelocateSpecDiskLocator diskLocator = new VirtualMachineRelocateSpecDiskLocator();
diskLocator.setDatastore(morVolumeDatastore);
Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, volume.getPath() + VMDK_EXTENSION);
String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first());
String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(diskInfo.first());
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
}

View File

@ -2271,7 +2271,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
"Please re-try when virtual disk is attached to a VM using a SCSI controller.");
}
String vmdkAbsFile = resource.getAbsoluteVmdkFile(vDisk);
String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(vDisk);
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);

View File

@ -33,7 +33,7 @@ import org.apache.cloudstack.api.response.ZoneResponse;
import com.cloud.exception.DiscoveryException;
import com.cloud.exception.ResourceInUseException;
import com.cloud.hypervisor.vmware.VmwareDatacenterService;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;

View File

@ -0,0 +1,139 @@
// 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.zone;
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.hypervisor.vmware.VmwareDatacenterService;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.Parameter;
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.VmwareDatacenterResponse;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@APICommand(name = "listVmwareDcVms", responseObject = UnmanagedInstanceResponse.class,
description = "Lists the VMs in a VMware Datacenter",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class ListVmwareDcVmsCmd extends BaseListCmd {
@Inject
public VmwareDatacenterService _vmwareDatacenterService;
@Parameter(name = ApiConstants.EXISTING_VCENTER_ID,
type = CommandType.UUID,
entityType = VmwareDatacenterResponse.class,
description = "UUID of a linked existing vCenter")
private Long existingVcenterId;
@Parameter(name = ApiConstants.VCENTER,
type = CommandType.STRING,
description = "The name/ip of vCenter. Make sure it is IP address or full qualified domain name for host running vCenter server.")
private String vcenter;
@Parameter(name = ApiConstants.DATACENTER_NAME, type = CommandType.STRING, description = "Name of VMware datacenter.")
private String datacenterName;
@Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "The Username required to connect to resource.")
private String username;
@Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, description = "The password for specified username.")
private String password;
public String getVcenter() {
return vcenter;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getDatacenterName() {
return datacenterName;
}
public Long getExistingVcenterId() {
return existingVcenterId;
}
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
checkParameters();
try {
List<UnmanagedInstanceTO> vms = _vmwareDatacenterService.listVMsInDatacenter(this);
List<BaseResponse> baseResponseList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(vms)) {
for (UnmanagedInstanceTO vmwareVm : vms) {
UnmanagedInstanceResponse resp = _responseGenerator.createUnmanagedInstanceResponse(vmwareVm, null, null);
baseResponseList.add(resp);
}
}
List<BaseResponse> pagingList = com.cloud.utils.StringUtils.applyPagination(baseResponseList, this.getStartIndex(), this.getPageSizeVal());
if (CollectionUtils.isEmpty(pagingList)) {
pagingList = baseResponseList;
}
ListResponse<BaseResponse> response = new ListResponse<>();
response.setResponses(pagingList, baseResponseList.size());
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException e) {
String errorMsg = String.format("Error retrieving VMs from VMware VC: %s", e.getMessage());
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg);
}
}
private void checkParameters() {
if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Please provide an existing vCenter ID or a vCenter IP/Name, parameters are mutually exclusive");
}
if (existingVcenterId == null && StringUtils.isAnyBlank(vcenter, datacenterName, username, password)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Please set all the information for a vCenter IP/Name, datacenter, username and password");
}
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public String getCommandName() {
return "listvmwaredcvmsresponse";
}
}

View File

@ -39,7 +39,7 @@ import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.hypervisor.vmware.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.hypervisor.vmware.VmwareDatacenterService;
import com.cloud.user.Account;

View File

@ -30,7 +30,7 @@ import org.apache.cloudstack.api.response.VmwareDatacenterResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.log4j.Logger;
import com.cloud.hypervisor.vmware.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.hypervisor.vmware.VmwareDatacenterService;
import com.cloud.user.Account;

View File

@ -31,8 +31,6 @@
class="com.cloud.hypervisor.vmware.manager.VmwareManagerImpl" />
<bean id="vmwareContextFactory"
class="com.cloud.hypervisor.vmware.resource.VmwareContextFactory" />
<bean id="VmwareDatacenterDaoImpl"
class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterDaoImpl" />
<bean id="VmwareDatacenterZoneMapDaoImpl"
class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDaoImpl" />
<bean id="LegacyZoneDaoImpl" class="com.cloud.hypervisor.vmware.dao.LegacyZoneDaoImpl" />

View File

@ -27,6 +27,7 @@ import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.ClusterVSMMapDao;
import com.cloud.dc.dao.DataCenterDao;
@ -42,7 +43,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.hypervisor.vmware.dao.LegacyZoneDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
import com.cloud.hypervisor.vmware.manager.VmwareManagerImpl;
import com.cloud.network.NetworkModel;

View File

@ -38,10 +38,10 @@ import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostDetailsDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.vmware.VmwareDatacenter;
import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
import com.cloud.dc.VmwareDatacenter;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
@RunWith(MockitoJUnitRunner.class)

View File

@ -164,6 +164,8 @@ import org.apache.cloudstack.api.response.TemplatePermissionsResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.TrafficMonitorResponse;
import org.apache.cloudstack.api.response.TrafficTypeResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.api.response.UserDataResponse;
@ -213,6 +215,7 @@ import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.usage.Usage;
import org.apache.cloudstack.usage.UsageService;
import org.apache.cloudstack.usage.UsageTypes;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@ -5107,6 +5110,72 @@ public class ApiResponseHelper implements ResponseGenerator {
return response;
}
@Override
public UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) {
UnmanagedInstanceResponse response = new UnmanagedInstanceResponse();
response.setName(instance.getName());
if (cluster != null) {
response.setClusterId(cluster.getUuid());
response.setClusterName(cluster.getName());
} else if (instance.getClusterName() != null) {
response.setClusterName(instance.getClusterName());
}
if (host != null) {
response.setHostId(host.getUuid());
response.setHostName(host.getName());
} else if (instance.getHostName() != null) {
response.setHostName(instance.getHostName());
}
response.setPowerState(instance.getPowerState().toString());
response.setCpuCores(instance.getCpuCores());
response.setCpuSpeed(instance.getCpuSpeed());
response.setCpuCoresPerSocket(instance.getCpuCoresPerSocket());
response.setMemory(instance.getMemory());
response.setOperatingSystemId(instance.getOperatingSystemId());
response.setOperatingSystem(instance.getOperatingSystem());
response.setObjectName("unmanagedinstance");
if (instance.getDisks() != null) {
for (UnmanagedInstanceTO.Disk disk : instance.getDisks()) {
UnmanagedInstanceDiskResponse diskResponse = new UnmanagedInstanceDiskResponse();
diskResponse.setDiskId(disk.getDiskId());
if (StringUtils.isNotEmpty(disk.getLabel())) {
diskResponse.setLabel(disk.getLabel());
}
diskResponse.setCapacity(disk.getCapacity());
diskResponse.setController(disk.getController());
diskResponse.setControllerUnit(disk.getControllerUnit());
diskResponse.setPosition(disk.getPosition());
diskResponse.setImagePath(disk.getImagePath());
diskResponse.setDatastoreName(disk.getDatastoreName());
diskResponse.setDatastoreHost(disk.getDatastoreHost());
diskResponse.setDatastorePath(disk.getDatastorePath());
diskResponse.setDatastoreType(disk.getDatastoreType());
response.addDisk(diskResponse);
}
}
if (instance.getNics() != null) {
for (UnmanagedInstanceTO.Nic nic : instance.getNics()) {
NicResponse nicResponse = new NicResponse();
nicResponse.setId(nic.getNicId());
nicResponse.setNetworkName(nic.getNetwork());
nicResponse.setMacAddress(nic.getMacAddress());
if (StringUtils.isNotEmpty(nic.getAdapterType())) {
nicResponse.setAdapterType(nic.getAdapterType());
}
if (!CollectionUtils.isEmpty(nic.getIpAddress())) {
nicResponse.setIpAddresses(nic.getIpAddress());
}
nicResponse.setVlanId(nic.getVlan());
nicResponse.setIsolatedPvlanId(nic.getPvlan());
nicResponse.setIsolatedPvlanType(nic.getPvlanType());
response.addNic(nicResponse);
}
}
return response;
}
@Override
public SecondaryStorageHeuristicsResponse createSecondaryStorageSelectorResponse(Heuristic heuristic) {
String zoneUuid = ApiDBUtils.findZoneById(heuristic.getZoneId()).getUuid();

View File

@ -28,6 +28,7 @@ import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationSe
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
@ -366,4 +367,15 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
};
}
@Override
public UnmanagedInstanceTO cloneHypervisorVMOutOfBand(String hostIp, String vmName, Map<String, String> params) {
s_logger.error("Unsupported operation: cannot clone external VM");
return null;
}
@Override
public boolean removeClonedHypervisorVMOutOfBand(String hostIp, String vmName, Map<String, String> params) {
s_logger.error("Unsupported operation: cannot remove external VM");
return false;
}
}

View File

@ -3673,7 +3673,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
SecStorageVMAutoScaleDown,
MountDisabledStoragePool,
VmwareCreateCloneFull,
VmwareAllowParallelExecution
VmwareAllowParallelExecution,
ConvertVmwareInstanceToKvmTimeout
};
}

View File

@ -4487,17 +4487,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
vm.setDisplayVm(true);
}
if (isImport) {
vm.setDataCenterId(zone.getId());
vm.setHostId(host.getId());
if (lastHost != null) {
vm.setLastHostId(lastHost.getId());
}
vm.setPowerState(powerState);
if (powerState == VirtualMachine.PowerState.PowerOn) {
vm.setState(State.Running);
}
}
setVmRequiredFieldsForImport(isImport, vm, zone, hypervisorType, host, lastHost, powerState);
vm.setUserVmType(vmType);
_vmDao.persist(vm);
@ -4585,6 +4575,23 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return vm;
}
protected void setVmRequiredFieldsForImport(boolean isImport, UserVmVO vm, DataCenter zone, HypervisorType hypervisorType,
Host host, Host lastHost, VirtualMachine.PowerState powerState) {
if (isImport) {
vm.setDataCenterId(zone.getId());
if (hypervisorType == HypervisorType.VMware) {
vm.setHostId(host.getId());
}
if (lastHost != null) {
vm.setLastHostId(lastHost.getId());
}
vm.setPowerState(powerState);
if (powerState == VirtualMachine.PowerState.PowerOn) {
vm.setState(State.Running);
}
}
}
private void updateVMDiskController(UserVmVO vm, Map<String, String> customParameters, GuestOSVO guestOS) {
// If hypervisor is vSphere and OS is OS X, set special settings.
if (guestOS.getDisplayName().toLowerCase().contains("apple mac os")) {
@ -8204,7 +8211,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (zone == null) {
throw new InvalidParameterValueException("Unable to import virtual machine with invalid zone");
}
if (host == null) {
if (host == null && hypervisorType == HypervisorType.VMware) {
throw new InvalidParameterValueException("Unable to import virtual machine with invalid host");
}

View File

@ -18,32 +18,58 @@
package org.apache.cloudstack.vm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.agent.api.ConvertInstanceAnswer;
import com.cloud.agent.api.ConvertInstanceCommand;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.RemoteInstanceTO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventVO;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.host.Host;
import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.resource.ResourceState;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.vm.VirtualMachineName;
import org.apache.cloudstack.api.ApiCommandResourceType;
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.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.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
@ -77,7 +103,6 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.UnsupportedServiceException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
@ -143,6 +168,8 @@ import com.google.gson.Gson;
public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso";
private static final Logger LOGGER = Logger.getLogger(UnmanagedVMsManagerImpl.class);
private static final List<Hypervisor.HypervisorType> importUnmanagedInstancesSupportedHypervisors =
Arrays.asList(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM);
@Inject
private AgentManager agentManager;
@ -206,6 +233,16 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
private SnapshotDao snapshotDao;
@Inject
private UserVmDao userVmDao;
@Inject
private HypervisorGuruManager hypervisorGuruManager;
@Inject
private VmwareDatacenterDao vmwareDatacenterDao;
@Inject
private ImageStoreDao imageStoreDao;
@Inject
private StoragePoolHostDao storagePoolHostDao;
@Inject
private DataStoreManager dataStoreManager;
protected Gson gson;
@ -232,66 +269,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return template;
}
private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) {
UnmanagedInstanceResponse response = new UnmanagedInstanceResponse();
response.setName(instance.getName());
if (cluster != null) {
response.setClusterId(cluster.getUuid());
}
if (host != null) {
response.setHostId(host.getUuid());
response.setHostName(host.getName());
}
response.setPowerState(instance.getPowerState().toString());
response.setCpuCores(instance.getCpuCores());
response.setCpuSpeed(instance.getCpuSpeed());
response.setCpuCoresPerSocket(instance.getCpuCoresPerSocket());
response.setMemory(instance.getMemory());
response.setOperatingSystemId(instance.getOperatingSystemId());
response.setOperatingSystem(instance.getOperatingSystem());
response.setObjectName("unmanagedinstance");
if (instance.getDisks() != null) {
for (UnmanagedInstanceTO.Disk disk : instance.getDisks()) {
UnmanagedInstanceDiskResponse diskResponse = new UnmanagedInstanceDiskResponse();
diskResponse.setDiskId(disk.getDiskId());
if (StringUtils.isNotEmpty(disk.getLabel())) {
diskResponse.setLabel(disk.getLabel());
}
diskResponse.setCapacity(disk.getCapacity());
diskResponse.setController(disk.getController());
diskResponse.setControllerUnit(disk.getControllerUnit());
diskResponse.setPosition(disk.getPosition());
diskResponse.setImagePath(disk.getImagePath());
diskResponse.setDatastoreName(disk.getDatastoreName());
diskResponse.setDatastoreHost(disk.getDatastoreHost());
diskResponse.setDatastorePath(disk.getDatastorePath());
diskResponse.setDatastoreType(disk.getDatastoreType());
response.addDisk(diskResponse);
}
}
if (instance.getNics() != null) {
for (UnmanagedInstanceTO.Nic nic : instance.getNics()) {
NicResponse nicResponse = new NicResponse();
nicResponse.setId(nic.getNicId());
nicResponse.setNetworkName(nic.getNetwork());
nicResponse.setMacAddress(nic.getMacAddress());
if (StringUtils.isNotEmpty(nic.getAdapterType())) {
nicResponse.setAdapterType(nic.getAdapterType());
}
if (!CollectionUtils.isEmpty(nic.getIpAddress())) {
nicResponse.setIpAddresses(nic.getIpAddress());
}
nicResponse.setVlanId(nic.getVlan());
nicResponse.setIsolatedPvlanId(nic.getPvlan());
nicResponse.setIsolatedPvlanType(nic.getPvlanType());
response.addNic(nicResponse);
}
}
return response;
}
private List<String> getAdditionalNameFilters(Cluster cluster) {
List<String> additionalNameFilter = new ArrayList<>();
if (cluster == null) {
@ -565,7 +542,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
}
private void checkUnmanagedNicAndNetworkForImport(String instanceName, UnmanagedInstanceTO.Nic nic, Network network, final DataCenter zone, final Account owner, final boolean autoAssign) throws ServerApiException {
private void checkUnmanagedNicAndNetworkForImport(String instanceName, UnmanagedInstanceTO.Nic nic, Network network, final DataCenter zone, final Account owner, final boolean autoAssign, Hypervisor.HypervisorType hypervisorType) throws ServerApiException {
basicNetworkChecks(instanceName, nic, network);
if (network.getDataCenterId() != zone.getId()) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network(ID: %s) for nic(ID: %s) belongs to a different zone than VM to be imported", network.getUuid(), nic.getNicId()));
@ -575,16 +552,18 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return;
}
String networkBroadcastUri = network.getBroadcastUri() == null ? null : network.getBroadcastUri().toString();
if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() == null &&
(StringUtils.isEmpty(networkBroadcastUri) ||
!networkBroadcastUri.equals(String.format("vlan://%d", nic.getVlan())))) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) vlan://%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan()));
}
String pvLanType = nic.getPvlanType() == null ? "" : nic.getPvlanType().toLowerCase().substring(0, 1);
if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() != null && nic.getPvlan() != 0 &&
(StringUtils.isEmpty(networkBroadcastUri) || !String.format("pvlan://%d-%s%d", nic.getVlan(), pvLanType, nic.getPvlan()).equals(networkBroadcastUri))) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("PVLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) pvlan://%d-%s%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan(), pvLanType, nic.getPvlan()));
if (hypervisorType == Hypervisor.HypervisorType.VMware) {
String networkBroadcastUri = network.getBroadcastUri() == null ? null : network.getBroadcastUri().toString();
if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() == null &&
(StringUtils.isEmpty(networkBroadcastUri) ||
!networkBroadcastUri.equals(String.format("vlan://%d", nic.getVlan())))) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) vlan://%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan()));
}
String pvLanType = nic.getPvlanType() == null ? "" : nic.getPvlanType().toLowerCase().substring(0, 1);
if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() != null && nic.getPvlan() != 0 &&
(StringUtils.isEmpty(networkBroadcastUri) || !String.format("pvlan://%d-%s%d", nic.getVlan(), pvLanType, nic.getPvlan()).equals(networkBroadcastUri))) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("PVLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) pvlan://%d-%s%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan(), pvLanType, nic.getPvlan()));
}
}
}
@ -621,10 +600,11 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
}
private Map<String, Long> getUnmanagedNicNetworkMap(String instanceName, List<UnmanagedInstanceTO.Nic> nics, final Map<String, Long> callerNicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap, final DataCenter zone, final String hostName, final Account owner) throws ServerApiException {
private Map<String, Long> getUnmanagedNicNetworkMap(String instanceName, List<UnmanagedInstanceTO.Nic> nics, final Map<String, Long> callerNicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap, final DataCenter zone, final String hostName, final Account owner, Hypervisor.HypervisorType hypervisorType) throws ServerApiException {
Map<String, Long> nicNetworkMap = new HashMap<>();
String nicAdapter = null;
for (UnmanagedInstanceTO.Nic nic : nics) {
for (int i = 0; i < nics.size(); i++) {
UnmanagedInstanceTO.Nic nic = nics.get(i);
if (StringUtils.isEmpty(nicAdapter)) {
nicAdapter = nic.getAdapterType();
} else {
@ -646,10 +626,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
continue;
}
try {
checkUnmanagedNicAndNetworkForImport(instanceName, nic, networkVO, zone, owner, true);
checkUnmanagedNicAndNetworkForImport(instanceName, nic, networkVO, zone, owner, true, hypervisorType);
network = networkVO;
} catch (Exception e) {
LOGGER.error(String.format("Error when checking NIC [%s] of unmanaged instance to import due to [%s]." , nic.getNicId(), e.getMessage()), e);
LOGGER.error(String.format("Error when checking NIC [%s] of unmanaged instance to import due to [%s].", nic.getNicId(), e.getMessage()), e);
}
if (network != null) {
checkUnmanagedNicAndNetworkHostnameForImport(instanceName, nic, network, hostName);
@ -660,7 +640,11 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
} else {
network = networkDao.findById(callerNicNetworkMap.get(nic.getNicId()));
checkUnmanagedNicAndNetworkForImport(instanceName, nic, network, zone, owner, false);
boolean autoImport = false;
if (hypervisorType == Hypervisor.HypervisorType.KVM) {
autoImport = ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equalsIgnoreCase("auto");
}
checkUnmanagedNicAndNetworkForImport(instanceName, nic, network, zone, owner, autoImport, hypervisorType);
checkUnmanagedNicAndNetworkHostnameForImport(instanceName, nic, network, hostName);
checkUnmanagedNicIpAndNetworkForImport(instanceName, nic, network, ipAddresses);
}
@ -678,7 +662,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
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)) {
if (vm.getHypervisorType() == Hypervisor.HypervisorType.VMware && 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()});
@ -927,7 +911,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
}
if (!migrateAllowed && !hostSupportsServiceOffering(host, validatedServiceOffering)) {
if (!migrateAllowed && host != null && !hostSupportsServiceOffering(host, validatedServiceOffering)) {
throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with host: %s of unmanaged VM: %s", serviceOffering.getUuid(), host.getUuid(), instanceName));
}
// Check disks and supplied disk offerings
@ -955,7 +939,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
// Check NICs and supplied networks
Map<String, Network.IpAddresses> nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap);
Map<String, Long> allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner);
Map<String, Long> allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, cluster.getHypervisorType());
if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) {
allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType());
}
@ -1047,7 +1031,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return unmanagedInstances;
}
private Cluster basicAccessChecks(Long clusterId) {
protected Cluster basicAccessChecks(Long clusterId) {
final Account caller = CallContext.current().getCallingAccount();
if (caller.getType() != Account.Type.ADMIN) {
throw new PermissionDeniedException(String.format("Cannot perform this operation, caller account [%s] is not ROOT Admin.", caller.getUuid()));
@ -1059,7 +1043,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
if (cluster == null) {
throw new InvalidParameterValueException(String.format("Cluster with ID [%d] cannot be found.", clusterId));
}
if (cluster.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
if (!importUnmanagedInstancesSupportedHypervisors.contains(cluster.getHypervisorType())) {
throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor [%s].", cluster.getHypervisorType().toString()));
}
return cluster;
@ -1069,7 +1053,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
public ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd) {
Long clusterId = cmd.getClusterId();
Cluster cluster = basicAccessChecks(clusterId);
String keyword = cmd.getKeyword();
if (StringUtils.isNotEmpty(keyword)) {
keyword = keyword.toLowerCase();
@ -1088,7 +1071,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
!instance.getName().toLowerCase().contains(keyword)) {
continue;
}
responses.add(createUnmanagedInstanceResponse(instance, cluster, host));
responses.add(responseGenerator.createUnmanagedInstanceResponse(instance, cluster, host));
}
}
ListResponse<UnmanagedInstanceResponse> listResponses = new ListResponse<>();
@ -1098,78 +1081,36 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
@Override
public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) {
return baseImportInstance(cmd);
}
/**
* Base logic for import virtual machines (unmanaged, external) into CloudStack
* @param cmd importVM or importUnmanagedInstance command
* @return imported user vm
*/
private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) {
basicParametersCheckForImportInstance(cmd.getName(), cmd.getDomainId(), cmd.getAccountName());
final String instanceName = cmd.getName();
Long clusterId = cmd.getClusterId();
Cluster cluster = basicAccessChecks(clusterId);
final Account caller = CallContext.current().getCallingAccount();
final DataCenter zone = dataCenterDao.findById(cluster.getDataCenterId());
final String instanceName = cmd.getName();
if (StringUtils.isEmpty(instanceName)) {
throw new InvalidParameterValueException("Instance name cannot be empty");
}
if (cmd.getDomainId() != null && StringUtils.isEmpty(cmd.getAccountName())) {
throw new InvalidParameterValueException(String.format("%s parameter must be specified with %s parameter", ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT));
}
final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
long userId = CallContext.current().getCallingUserId();
List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
if (CollectionUtils.isNotEmpty(userVOs)) {
userId = userVOs.get(0).getId();
}
VMTemplateVO template;
final Long templateId = cmd.getTemplateId();
if (templateId == null) {
template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
if (template == null) {
template = createDefaultDummyVmImportTemplate();
if (template == null) {
throw new InvalidParameterValueException(String.format("Default VM import template with unique name: %s for hypervisor: %s cannot be created. Please use templateid parameter for import", VM_IMPORT_DEFAULT_TEMPLATE_NAME, cluster.getHypervisorType().toString()));
}
}
} else {
template = templateDao.findById(templateId);
}
if (template == null) {
throw new InvalidParameterValueException(String.format("Template ID: %d cannot be found", templateId));
}
final Long serviceOfferingId = cmd.getServiceOfferingId();
if (serviceOfferingId == null) {
throw new InvalidParameterValueException("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("Please provide a valid hostname for the VM. VM name contains unsupported characters that cannot 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");
}
if (cluster.getHypervisorType().equals(Hypervisor.HypervisorType.VMware) &&
Boolean.parseBoolean(configurationDao.getValue(Config.SetVmInternalNameUsingDisplayName.key()))) {
// If global config vm.instancename.flag is set to true, then CS will set guest VM's name as it appears on the hypervisor, to its hostname.
// In case of VMware since VM name must be unique within a DC, check if VM with the same hostname already exists in the zone.
VMInstanceVO vmByHostName = vmDao.findVMByHostNameInZone(hostName, zone.getId());
if (vmByHostName != null && vmByHostName.getState() != VirtualMachine.State.Expunging) {
throw new InvalidParameterValueException(String.format("Failed to import VM: %s. There already exists a VM by the hostname: %s in zone: %s", instanceName, hostName, zone.getUuid()));
}
}
long userId = getUserIdForImportInstance(owner);
VMTemplateVO template = getTemplateForImportInstance(cmd.getTemplateId(), cluster.getHypervisorType());
ServiceOfferingVO serviceOffering = getServiceOfferingForImportInstance(cmd.getServiceOfferingId(), owner, zone);
checkResourceLimitForImportInstance(owner);
String displayName = getDisplayNameForImportInstance(cmd.getDisplayName(), instanceName);
String hostName = getHostNameForImportInstance(cmd.getHostName(), cluster.getHypervisorType(), instanceName, displayName);
checkVmwareInstanceNameForImportInstance(cluster.getHypervisorType(), instanceName, hostName, zone);
final Map<String, Long> nicNetworkMap = cmd.getNicNetworkList();
final Map<String, Network.IpAddresses> nicIpAddressMap = cmd.getNicIpAddressList();
final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
@ -1180,8 +1121,150 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
List<String> managedVms = new ArrayList<>(additionalNameFilters);
managedVms.addAll(getHostsManagedVms(hosts));
ActionEventUtils.onStartedActionEvent(userId, owner.getId(), EventTypes.EVENT_VM_IMPORT,
cmd.getEventDescription(), null, null, true, 0);
//TODO: Placeholder for integration with KVM ingestion and KVM extend unmanage/manage VMs
if (cmd instanceof ImportVmCmd) {
ImportVmCmd importVmCmd = (ImportVmCmd) cmd;
if (StringUtils.isBlank(importVmCmd.getImportSource())) {
throw new CloudRuntimeException("Please provide an import source for importing the VM");
}
String source = importVmCmd.getImportSource().toUpperCase();
ImportSource importSource = Enum.valueOf(ImportSource.class, source);
if (ImportSource.VMWARE == importSource) {
userVm = importUnmanagedInstanceFromVmwareToKvm(zone, cluster,
template, instanceName, displayName, hostName, caller, owner, userId,
serviceOffering, dataDiskOfferingMap,
nicNetworkMap, nicIpAddressMap,
details, importVmCmd, forced);
}
} else {
if (cluster.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
userVm = importUnmanagedInstanceFromVmwareToVmware(zone, cluster, hosts, additionalNameFilters,
template, instanceName, displayName, hostName, caller, owner, userId,
serviceOffering, dataDiskOfferingMap,
nicNetworkMap, nicIpAddressMap,
details, cmd.getMigrateAllowed(), managedVms, forced);
}
}
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);
}
private long getUserIdForImportInstance(Account owner) {
long userId = CallContext.current().getCallingUserId();
List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
if (CollectionUtils.isNotEmpty(userVOs)) {
userId = userVOs.get(0).getId();
}
return userId;
}
protected void basicParametersCheckForImportInstance(String name, Long domainId, String accountName) {
if (StringUtils.isEmpty(name)) {
throw new InvalidParameterValueException("Instance name cannot be empty");
}
if (domainId != null && StringUtils.isEmpty(accountName)) {
throw new InvalidParameterValueException(String.format("%s parameter must be specified with %s parameter", ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT));
}
}
private void checkVmwareInstanceNameForImportInstance(Hypervisor.HypervisorType hypervisorType, String instanceName, String hostName, DataCenter zone) {
if (hypervisorType.equals(Hypervisor.HypervisorType.VMware) &&
Boolean.parseBoolean(configurationDao.getValue(Config.SetVmInternalNameUsingDisplayName.key()))) {
// If global config vm.instancename.flag is set to true, then CS will set guest VM's name as it appears on the hypervisor, to its hostname.
// In case of VMware since VM name must be unique within a DC, check if VM with the same hostname already exists in the zone.
VMInstanceVO vmByHostName = vmDao.findVMByHostNameInZone(hostName, zone.getId());
if (vmByHostName != null && vmByHostName.getState() != VirtualMachine.State.Expunging) {
throw new InvalidParameterValueException(String.format("Failed to import VM: %s. There already exists a VM by the hostname: %s in zone: %s", instanceName, hostName, zone.getUuid()));
}
}
}
private String getHostNameForImportInstance(String hostName, Hypervisor.HypervisorType hypervisorType,
String instanceName, String displayName) {
if (StringUtils.isEmpty(hostName)) {
hostName = hypervisorType == Hypervisor.HypervisorType.VMware ? instanceName : displayName;
if (!NetUtils.verifyDomainNameLabel(hostName, true)) {
throw new InvalidParameterValueException("Please provide a valid hostname for the VM. VM name contains unsupported characters that cannot be used as hostname.");
}
}
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");
}
return hostName;
}
private String getDisplayNameForImportInstance(String displayName, String instanceName) {
return StringUtils.isEmpty(displayName) ? instanceName : displayName;
}
private void checkResourceLimitForImportInstance(Account owner) {
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())));
}
}
private ServiceOfferingVO getServiceOfferingForImportInstance(Long serviceOfferingId, Account owner, DataCenter zone) {
if (serviceOfferingId == null) {
throw new InvalidParameterValueException("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);
return serviceOffering;
}
protected VMTemplateVO getTemplateForImportInstance(Long templateId, Hypervisor.HypervisorType hypervisorType) {
VMTemplateVO template;
if (templateId == null) {
template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
if (template == null) {
template = createDefaultDummyVmImportTemplate();
if (template == null) {
throw new InvalidParameterValueException(String.format("Default VM import template with unique name: %s for hypervisor: %s cannot be created. Please use templateid parameter for import", VM_IMPORT_DEFAULT_TEMPLATE_NAME, hypervisorType.toString()));
}
}
} else {
template = templateDao.findById(templateId);
}
if (template == null) {
throw new InvalidParameterValueException(String.format("Template ID: %d cannot be found", templateId));
}
return template;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_IMPORT, eventDescription = "importing VM", async = true)
public UserVmResponse importVm(ImportVmCmd cmd) {
return baseImportInstance(cmd);
}
private UserVm importUnmanagedInstanceFromVmwareToVmware(DataCenter zone, Cluster cluster,
List<HostVO> hosts, List<String> additionalNameFilters,
VMTemplateVO template, String instanceName, String displayName,
String hostName, Account caller, Account owner, long userId,
ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
Map<String, String> details, Boolean migrateAllowed, List<String> managedVms, boolean forced) {
UserVm userVm = null;
for (HostVO host : hosts) {
HashMap<String, UnmanagedInstanceTO> unmanagedInstances = getUnmanagedInstancesForHost(host, cmd.getName(), managedVms);
HashMap<String, UnmanagedInstanceTO> unmanagedInstances = getUnmanagedInstancesForHost(host, instanceName, managedVms);
if (MapUtils.isEmpty(unmanagedInstances)) {
continue;
}
@ -1219,17 +1302,329 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
template, displayName, hostName, CallContext.current().getCallingAccount(), owner, userId,
serviceOffering, dataDiskOfferingMap,
nicNetworkMap, nicIpAddressMap,
details, cmd.getMigrateAllowed(), forced);
details, migrateAllowed, forced);
break;
}
if (userVm != null) {
break;
}
}
if (userVm == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to find unmanaged vm with name: %s in cluster: %s", instanceName, cluster.getUuid()));
return userVm;
}
private UnmanagedInstanceTO cloneSourceVmwareUnmanagedInstance(String vcenter, String datacenterName, String username, String password, String clusterName, String sourceHostName, String sourceVM) {
HypervisorGuru vmwareGuru = hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware);
Map<String, String> params = createParamsForTemplateFromVmwareVmMigration(vcenter, datacenterName,
username, password, clusterName, sourceHostName, sourceVM);
return vmwareGuru.cloneHypervisorVMOutOfBand(sourceHostName, sourceVM, params);
}
protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster destinationCluster, VMTemplateVO template,
String sourceVM, String displayName, String hostName,
Account caller, Account owner, long userId,
ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
Map<String, String> details, ImportVmCmd cmd, boolean forced) {
Long existingVcenterId = cmd.getExistingVcenterId();
String vcenter = cmd.getVcenter();
String datacenterName = cmd.getDatacenterName();
String username = cmd.getUsername();
String password = cmd.getPassword();
String clusterName = cmd.getClusterName();
String sourceHostName = cmd.getHost();
Long convertInstanceHostId = cmd.getConvertInstanceHostId();
Long convertStoragePoolId = cmd.getConvertStoragePoolId();
if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Please provide an existing vCenter ID or a vCenter IP/Name, parameters are mutually exclusive");
}
return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0);
if (existingVcenterId == null && StringUtils.isAnyBlank(vcenter, datacenterName, username, password)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Please set all the information for a vCenter IP/Name, datacenter, username and password");
}
if (existingVcenterId != null) {
VmwareDatacenterVO existingDC = vmwareDatacenterDao.findById(existingVcenterId);
if (existingDC == null) {
String err = String.format("Cannot find any existing Vmware DC with ID %s", existingVcenterId);
LOGGER.error(err);
throw new CloudRuntimeException(err);
}
vcenter = existingDC.getVcenterHost();
datacenterName = existingDC.getVmwareDatacenterName();
username = existingDC.getUser();
password = existingDC.getPassword();
}
UnmanagedInstanceTO clonedInstance = null;
try {
String instanceName = getGeneratedInstanceName(owner);
clonedInstance = cloneSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password,
clusterName, sourceHostName, sourceVM);
checkNetworkingBeforeConvertingVmwareInstance(zone, owner, instanceName, hostName, clonedInstance, nicNetworkMap, nicIpAddressMap, forced);
UnmanagedInstanceTO convertedInstance = convertVmwareInstanceToKVM(vcenter, datacenterName, clusterName, username, password,
sourceHostName, clonedInstance, destinationCluster, convertInstanceHostId, convertStoragePoolId);
sanitizeConvertedInstance(convertedInstance, clonedInstance);
UserVm userVm = importVirtualMachineInternal(convertedInstance, instanceName, zone, destinationCluster, null,
template, displayName, hostName, caller, owner, userId,
serviceOffering, dataDiskOfferingMap,
nicNetworkMap, nicIpAddressMap,
details, false, forced);
LOGGER.debug(String.format("VM %s imported successfully", sourceVM));
return userVm;
} catch (CloudRuntimeException e) {
LOGGER.error(String.format("Error importing VM: %s", e.getMessage()), e);
ActionEventUtils.onCompletedActionEvent(userId, owner.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_IMPORT,
cmd.getEventDescription(), null, null, 0);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
} finally {
removeClonedInstance(vcenter, datacenterName, username, password, sourceHostName, clonedInstance.getName(), sourceVM);
}
}
private void checkNetworkingBeforeConvertingVmwareInstance(DataCenter zone, Account owner, String instanceName,
String hostName, UnmanagedInstanceTO clonedInstance,
Map<String, Long> nicNetworkMap,
Map<String, Network.IpAddresses> nicIpAddressMap,
boolean forced) {
List<UnmanagedInstanceTO.Nic> nics = clonedInstance.getNics();
List<Long> networkIds = new ArrayList<>(nicNetworkMap.values());
if (nics.size() != networkIds.size()) {
String msg = String.format("Different number of nics found on instance %s: %s vs %s nics provided",
clonedInstance.getName(), nics.size(), networkIds.size());
LOGGER.error(msg);
throw new CloudRuntimeException(msg);
}
for (UnmanagedInstanceTO.Nic nic : nics) {
Long networkId = nicNetworkMap.get(nic.getNicId());
NetworkVO network = networkDao.findById(networkId);
if (network == null) {
String err = String.format("Cannot find a network with id = %s", networkId);
LOGGER.error(err);
throw new CloudRuntimeException(err);
}
Network.IpAddresses ipAddresses = null;
if (MapUtils.isNotEmpty(nicIpAddressMap) && nicIpAddressMap.containsKey(nic.getNicId())) {
ipAddresses = nicIpAddressMap.get(nic.getNicId());
}
boolean autoImport = ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equalsIgnoreCase("auto");
checkUnmanagedNicAndNetworkMacAddressForImport(network, nic, forced);
checkUnmanagedNicAndNetworkForImport(instanceName, nic, network, zone, owner, autoImport, Hypervisor.HypervisorType.KVM);
checkUnmanagedNicAndNetworkHostnameForImport(instanceName, nic, network, hostName);
checkUnmanagedNicIpAndNetworkForImport(instanceName, nic, network, ipAddresses);
}
}
private void checkUnmanagedNicAndNetworkMacAddressForImport(NetworkVO network, UnmanagedInstanceTO.Nic nic, boolean forced) {
NicVO existingNic = nicDao.findByNetworkIdAndMacAddress(network.getId(), nic.getMacAddress());
if (existingNic != null && !forced) {
String err = String.format("NIC with MAC address = %s exists on network with ID = %s and forced flag is disabled",
nic.getMacAddress(), network.getId());
LOGGER.error(err);
throw new CloudRuntimeException(err);
}
}
private String getGeneratedInstanceName(Account owner) {
long id = vmDao.getNextInSequence(Long.class, "id");
String instanceSuffix = configurationDao.getValue(Config.InstanceName.key());
if (instanceSuffix == null) {
instanceSuffix = "DEFAULT";
}
return VirtualMachineName.getVmName(id, owner.getId(), instanceSuffix);
}
private void sanitizeConvertedInstance(UnmanagedInstanceTO convertedInstance, UnmanagedInstanceTO clonedInstance) {
convertedInstance.setCpuCores(clonedInstance.getCpuCores());
convertedInstance.setCpuSpeed(clonedInstance.getCpuSpeed());
convertedInstance.setCpuCoresPerSocket(clonedInstance.getCpuCoresPerSocket());
convertedInstance.setMemory(clonedInstance.getMemory());
convertedInstance.setPowerState(UnmanagedInstanceTO.PowerState.PowerOff);
List<UnmanagedInstanceTO.Disk> convertedInstanceDisks = convertedInstance.getDisks();
List<UnmanagedInstanceTO.Disk> clonedInstanceDisks = clonedInstance.getDisks();
for (int i = 0; i < convertedInstanceDisks.size(); i++) {
UnmanagedInstanceTO.Disk disk = convertedInstanceDisks.get(i);
disk.setDiskId(clonedInstanceDisks.get(i).getDiskId());
}
List<UnmanagedInstanceTO.Nic> convertedInstanceNics = convertedInstance.getNics();
List<UnmanagedInstanceTO.Nic> clonedInstanceNics = clonedInstance.getNics();
if (CollectionUtils.isEmpty(convertedInstanceNics) && CollectionUtils.isNotEmpty(clonedInstanceNics)) {
for (UnmanagedInstanceTO.Nic nic : clonedInstanceNics) {
// In case the NICs information is not parsed from the converted XML domain, use the cloned instance NICs with virtio adapter
nic.setAdapterType("virtio");
}
convertedInstance.setNics(clonedInstanceNics);
} else {
for (int i = 0; i < convertedInstanceNics.size(); i++) {
UnmanagedInstanceTO.Nic nic = convertedInstanceNics.get(i);
nic.setNicId(clonedInstanceNics.get(i).getNicId());
}
}
}
private void removeClonedInstance(String vcenter, String datacenterName,
String username, String password,
String sourceHostName, String clonedInstanceName,
String sourceVM) {
HypervisorGuru vmwareGuru = hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware);
Map<String, String> params = createParamsForRemoveClonedInstance(vcenter, datacenterName, username, password, sourceVM);
boolean result = vmwareGuru.removeClonedHypervisorVMOutOfBand(sourceHostName, clonedInstanceName, params);
if (!result) {
String msg = String.format("Could not properly remove the cloned instance %s from VMware datacenter %s:%s",
clonedInstanceName, vcenter, datacenterName);
LOGGER.warn(msg);
return;
}
LOGGER.debug(String.format("Removed the cloned instance %s from VMWare datacenter %s:%s",
clonedInstanceName, vcenter, datacenterName));
}
private Map<String, String> createParamsForRemoveClonedInstance(String vcenter, String datacenterName, String username,
String password, String sourceVM) {
Map<String, String> params = new HashMap<>();
params.put(VmDetailConstants.VMWARE_VCENTER_HOST, vcenter);
params.put(VmDetailConstants.VMWARE_DATACENTER_NAME, datacenterName);
params.put(VmDetailConstants.VMWARE_VCENTER_USERNAME, username);
params.put(VmDetailConstants.VMWARE_VCENTER_PASSWORD, password);
return params;
}
private HostVO selectInstanceConvertionKVMHostInCluster(Cluster destinationCluster, Long convertInstanceHostId) {
if (convertInstanceHostId != null) {
HostVO selectedHost = hostDao.findById(convertInstanceHostId);
if (selectedHost == null) {
String msg = String.format("Cannot find host with ID %s", convertInstanceHostId);
LOGGER.error(msg);
throw new CloudRuntimeException(msg);
}
if (selectedHost.getResourceState() != ResourceState.Enabled ||
selectedHost.getStatus() != Status.Up || selectedHost.getType() != Host.Type.Routing ||
selectedHost.getClusterId() != destinationCluster.getId()) {
String msg = String.format("Cannot perform the conversion on the host %s as it is not a running and Enabled host", selectedHost.getName());
LOGGER.error(msg);
throw new CloudRuntimeException(msg);
}
return selectedHost;
}
List<HostVO> hosts = hostDao.listByClusterAndHypervisorType(destinationCluster.getId(), destinationCluster.getHypervisorType());
if (CollectionUtils.isEmpty(hosts)) {
String err = String.format("Could not find any running %s host in cluster %s",
destinationCluster.getHypervisorType(), destinationCluster.getName());
LOGGER.error(err);
throw new CloudRuntimeException(err);
}
List<HostVO> filteredHosts = hosts.stream()
.filter(x -> x.getResourceState() == ResourceState.Enabled)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(filteredHosts)) {
String err = String.format("Could not find a %s host in cluster %s to perform the instance conversion",
destinationCluster.getHypervisorType(), destinationCluster.getName());
LOGGER.error(err);
throw new CloudRuntimeException(err);
}
return filteredHosts.get(new Random().nextInt(filteredHosts.size()));
}
private UnmanagedInstanceTO convertVmwareInstanceToKVM(String vcenter, String datacenterName, String clusterName,
String username, String password, String hostName,
UnmanagedInstanceTO clonedInstance, Cluster destinationCluster,
Long convertInstanceHostId, Long convertStoragePoolId) {
HostVO convertHost = selectInstanceConvertionKVMHostInCluster(destinationCluster, convertInstanceHostId);
String vmName = clonedInstance.getName();
LOGGER.debug(String.format("The host %s (%s) is selected to execute the conversion of the instance %s" +
" from VMware to KVM ", convertHost.getId(), convertHost.getName(), vmName));
RemoteInstanceTO remoteInstanceTO = new RemoteInstanceTO(hostName, vmName,
vcenter, datacenterName, clusterName, username, password);
DataStoreTO temporaryConvertLocation = selectInstanceConversionTemporaryLocation(destinationCluster, convertStoragePoolId, convertHost);
List<String> destinationStoragePools = selectInstanceConvertionStoragePools(destinationCluster, clonedInstance.getDisks());
ConvertInstanceCommand cmd = new ConvertInstanceCommand(remoteInstanceTO,
Hypervisor.HypervisorType.KVM, destinationStoragePools, temporaryConvertLocation);
int timeoutSeconds = StorageManager.ConvertVmwareInstanceToKvmTimeout.value() * 60 * 60;
cmd.setWait(timeoutSeconds);
Answer convertAnswer;
try {
convertAnswer = agentManager.send(convertHost.getId(), cmd);
} catch (AgentUnavailableException | OperationTimedoutException e) {
String err = String.format("Could not send the convert instance command to host %s (%s) due to: %s",
convertHost.getId(), convertHost.getName(), e.getMessage());
LOGGER.error(err, e);
throw new CloudRuntimeException(err);
}
if (!convertAnswer.getResult()) {
String err = String.format("The convert process failed for instance %s from Vmware to KVM on host %s: %s",
vmName, convertHost.getName(), convertAnswer.getDetails());
LOGGER.error(err);
throw new CloudRuntimeException(err);
}
return ((ConvertInstanceAnswer) convertAnswer).getConvertedInstance();
}
private List<String> selectInstanceConvertionStoragePools(Cluster destinationCluster, List<UnmanagedInstanceTO.Disk> disks) {
List<String> storagePools = new ArrayList<>(disks.size());
List<StoragePoolVO> pools = primaryDataStoreDao.listPoolsByCluster(destinationCluster.getId());
//TODO: Choose pools by capacity
for (UnmanagedInstanceTO.Disk disk : disks) {
Long capacity = disk.getCapacity();
storagePools.add(pools.get(0).getUuid());
}
return storagePools;
}
private void logFailureAndThrowException(String msg) {
LOGGER.error(msg);
throw new CloudRuntimeException(msg);
}
protected DataStoreTO selectInstanceConversionTemporaryLocation(Cluster destinationCluster, Long convertStoragePoolId, HostVO convertHost) {
if (convertStoragePoolId != null) {
StoragePoolVO selectedStoragePool = primaryDataStoreDao.findById(convertStoragePoolId);
if (selectedStoragePool == null) {
logFailureAndThrowException(String.format("Cannot find a storage pool with ID %s", convertStoragePoolId));
}
if ((selectedStoragePool.getScope() == ScopeType.CLUSTER && selectedStoragePool.getClusterId() != destinationCluster.getId()) ||
(selectedStoragePool.getScope() == ScopeType.ZONE && selectedStoragePool.getDataCenterId() != destinationCluster.getDataCenterId())) {
logFailureAndThrowException(String.format("Cannot use the storage pool %s for the instance conversion as " +
"it is not in the scope of the cluster %s", selectedStoragePool.getName(), destinationCluster.getName()));
}
if (selectedStoragePool.getScope() == ScopeType.HOST &&
storagePoolHostDao.findByPoolHost(selectedStoragePool.getId(), convertHost.getId()) == null) {
logFailureAndThrowException(String.format("The storage pool %s is not a local storage pool for the host %s", selectedStoragePool.getName(), convertHost.getName()));
} else if (selectedStoragePool.getPoolType() != Storage.StoragePoolType.NetworkFilesystem) {
logFailureAndThrowException(String.format("The storage pool %s is not supported for temporary conversion location, supported pools are NFS storage pools", selectedStoragePool.getName()));
}
return dataStoreManager.getPrimaryDataStore(convertStoragePoolId).getTO();
} else {
long zoneId = destinationCluster.getDataCenterId();
ImageStoreVO imageStore = imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs");
if (imageStore == null) {
logFailureAndThrowException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " +
"for instance conversion", zoneId));
}
DataStore dataStore = dataStoreManager.getDataStore(imageStore.getId(), DataStoreRole.Image);
return dataStore.getTO();
}
}
protected Map<String, String> createParamsForTemplateFromVmwareVmMigration(String vcenterHost, String datacenterName,
String username, String password,
String clusterName, String sourceHostName,
String sourceVMName) {
Map<String, String> params = new HashMap<>();
params.put(VmDetailConstants.VMWARE_VCENTER_HOST, vcenterHost);
params.put(VmDetailConstants.VMWARE_DATACENTER_NAME, datacenterName);
params.put(VmDetailConstants.VMWARE_VCENTER_USERNAME, username);
params.put(VmDetailConstants.VMWARE_VCENTER_PASSWORD, password);
params.put(VmDetailConstants.VMWARE_CLUSTER_NAME, clusterName);
params.put(VmDetailConstants.VMWARE_HOST_NAME, sourceHostName);
params.put(VmDetailConstants.VMWARE_VM_NAME, sourceVMName);
return params;
}
@Override
@ -1238,6 +1633,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
cmdList.add(ListUnmanagedInstancesCmd.class);
cmdList.add(ImportUnmanagedInstanceCmd.class);
cmdList.add(UnmanageVMInstanceCmd.class);
cmdList.add(ImportVmCmd.class);
return cmdList;
}

View File

@ -42,10 +42,13 @@ import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.usage.UsageService;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -60,6 +63,7 @@ import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
@ -367,4 +371,29 @@ public class ApiResponseHelperTest {
assertNull(response.getUserDataDetails());
}
}
private UnmanagedInstanceTO getUnmanagedInstaceForTests() {
UnmanagedInstanceTO instance = Mockito.mock(UnmanagedInstanceTO.class);
Mockito.when(instance.getPowerState()).thenReturn(UnmanagedInstanceTO.PowerState.PowerOff);
Mockito.when(instance.getClusterName()).thenReturn("CL1");
UnmanagedInstanceTO.Disk disk = Mockito.mock(UnmanagedInstanceTO.Disk.class);
Mockito.when(disk.getDiskId()).thenReturn("0");
Mockito.when(disk.getLabel()).thenReturn("Hard disk 1");
Mockito.when(disk.getCapacity()).thenReturn(17179869184L);
Mockito.when(disk.getPosition()).thenReturn(0);
Mockito.when(instance.getDisks()).thenReturn(List.of(disk));
UnmanagedInstanceTO.Nic nic = Mockito.mock(UnmanagedInstanceTO.Nic.class);
Mockito.when(nic.getNicId()).thenReturn("Network adapter 1");
Mockito.when(nic.getMacAddress()).thenReturn("aa:bb:cc:dd:ee:ff");
Mockito.when(instance.getNics()).thenReturn(List.of(nic));
return instance;
}
@Test
public void testCreateUnmanagedInstanceResponseVmwareDcVms() {
UnmanagedInstanceTO instance = getUnmanagedInstaceForTests();
UnmanagedInstanceResponse response = apiResponseHelper.createUnmanagedInstanceResponse(instance, null, null);
Assert.assertEquals(1, response.getDisks().size());
Assert.assertEquals(1, response.getNics().size());
}
}

View File

@ -116,6 +116,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@ -1200,4 +1201,24 @@ public class UserVmManagerImplTest {
Assert.assertEquals(expected, result);
Assert.assertEquals(expected, userVmVoMock.getPassword());
}
@Test
public void testSetVmRequiredFieldsForImportNotImport() {
userVmManagerImpl.setVmRequiredFieldsForImport(false, userVmVoMock, _dcMock,
Hypervisor.HypervisorType.VMware, Mockito.mock(HostVO.class), Mockito.mock(HostVO.class), VirtualMachine.PowerState.PowerOn);
Mockito.verify(userVmVoMock, never()).setDataCenterId(anyLong());
}
@Test
public void testSetVmRequiredFieldsForImportFromLastHost() {
HostVO lastHost = Mockito.mock(HostVO.class);
HostVO host = Mockito.mock(HostVO.class);
Mockito.when(_dcMock.getId()).thenReturn(1L);
Mockito.when(host.getId()).thenReturn(1L);
Mockito.when(lastHost.getId()).thenReturn(2L);
userVmManagerImpl.setVmRequiredFieldsForImport(true, userVmVoMock, _dcMock,
Hypervisor.HypervisorType.VMware, host, lastHost, VirtualMachine.PowerState.PowerOn);
Mockito.verify(userVmVoMock).setLastHostId(2L);
Mockito.verify(userVmVoMock).setState(VirtualMachine.State.Running);
}
}

View File

@ -19,16 +19,24 @@ package org.apache.cloudstack.vm;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.ConvertInstanceAnswer;
import com.cloud.agent.api.ConvertInstanceCommand;
import com.cloud.agent.api.GetUnmanagedInstancesAnswer;
import com.cloud.agent.api.GetUnmanagedInstancesCommand;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.configuration.Resource;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.VmwareDatacenterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.VmwareDatacenterDao;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.UsageEventUtils;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.UnsupportedServiceException;
import com.cloud.host.Host;
@ -36,21 +44,28 @@ import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.network.Network;
import com.cloud.network.NetworkModel;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.offering.ServiceOffering;
import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceState;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
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;
@ -64,6 +79,7 @@ import com.cloud.user.UserVO;
import com.cloud.user.dao.UserDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicProfile;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmManager;
@ -78,18 +94,25 @@ 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.response.UserVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
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;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
@ -104,15 +127,21 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class UnmanagedVMsManagerImplTest {
@InjectMocks
private UnmanagedVMsManager unmanagedVMsManager = new UnmanagedVMsManagerImpl();
private UnmanagedVMsManagerImpl unmanagedVMsManager = new UnmanagedVMsManagerImpl();
@Mock
private UserVmManager userVmManager;
@ -168,6 +197,16 @@ public class UnmanagedVMsManagerImplTest {
private NicDao nicDao;
@Mock
private HostDao hostDao;
@Mock
private VmwareDatacenterDao vmwareDatacenterDao;
@Mock
private HypervisorGuruManager hypervisorGuruManager;
@Mock
private ImageStoreDao imageStoreDao;
@Mock
private DataStoreManager dataStoreManager;
@Mock
private StoragePoolHostDao storagePoolHostDao;
@Mock
private VMInstanceVO virtualMachine;
@ -178,15 +217,24 @@ public class UnmanagedVMsManagerImplTest {
private AutoCloseable closeable;
private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
private UnmanagedInstanceTO instance;
@Before
public void setUp() throws Exception {
closeable = MockitoAnnotations.openMocks(this);
actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
BDDMockito.given(ActionEventUtils.onStartedActionEvent(anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString(), anyBoolean(), anyLong()))
.willReturn(1L);
BDDMockito.given(ActionEventUtils.onCompletedActionEvent(anyLong(), anyLong(), anyString(), anyString(), anyString(), anyLong(), anyString(), anyLong()))
.willReturn(1L);
AccountVO account = new AccountVO("admin", 1L, "", Account.Type.ADMIN, "uuid");
UserVO user = new UserVO(1, "adminuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
UnmanagedInstanceTO instance = new UnmanagedInstanceTO();
instance = new UnmanagedInstanceTO();
instance.setName("TestInstance");
instance.setCpuCores(2);
instance.setCpuCoresPerSocket(1);
@ -303,6 +351,7 @@ public class UnmanagedVMsManagerImplTest {
@After
public void tearDown() throws Exception {
closeable.close();
actionEventUtilsMocked.close();
CallContext.unregister();
}
@ -316,7 +365,7 @@ public class UnmanagedVMsManagerImplTest {
public void listUnmanagedInstancesInvalidHypervisorTest() {
ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class);
ClusterVO cluster = new ClusterVO(1, 1, "Cluster");
cluster.setHypervisorType(Hypervisor.HypervisorType.KVM.toString());
cluster.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString());
when(clusterDao.findById(Mockito.anyLong())).thenReturn(cluster);
unmanagedVMsManager.listUnmanagedInstances(cmd);
}
@ -389,4 +438,276 @@ public class UnmanagedVMsManagerImplTest {
public void unmanageVMInstanceExistingISOAttachedTest() {
unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
}
private void baseBasicParametersCheckForImportInstance(String name, Long domainId, String accountName) {
unmanagedVMsManager.basicParametersCheckForImportInstance(name, domainId, accountName);
}
@Test(expected = InvalidParameterValueException.class)
public void testBasicParametersCheckForImportInstanceMissingName() {
baseBasicParametersCheckForImportInstance(null, 1L, "test");
}
@Test(expected = InvalidParameterValueException.class)
public void testBasicParametersCheckForImportInstanceMissingDomainAndAccount() {
baseBasicParametersCheckForImportInstance("vm", 1L, "");
}
@Test(expected = InvalidParameterValueException.class)
public void testBasicAccessChecksMissingClusterId() {
unmanagedVMsManager.basicAccessChecks(null);
}
@Test(expected = PermissionDeniedException.class)
public void testBasicAccessChecksNotAdminCaller() {
CallContext.unregister();
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);
unmanagedVMsManager.basicAccessChecks(1L);
}
@Test(expected = InvalidParameterValueException.class)
public void testBasicAccessChecksUnsupportedHypervisorType() {
ClusterVO clusterVO = new ClusterVO(1L, 1L, "Cluster");
clusterVO.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString());
when(clusterDao.findById(Mockito.anyLong())).thenReturn(clusterVO);
unmanagedVMsManager.basicAccessChecks(1L);
}
@Test
public void testGetTemplateForImportInstanceDefaultTemplate() {
String defaultTemplateName = "DefaultTemplate";
VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
when(template.getName()).thenReturn(defaultTemplateName);
when(templateDao.findByName(anyString())).thenReturn(template);
VMTemplateVO templateForImportInstance = unmanagedVMsManager.getTemplateForImportInstance(null, Hypervisor.HypervisorType.KVM);
Assert.assertEquals(defaultTemplateName, templateForImportInstance.getName());
}
private enum VcenterParameter {
EXISTING, EXTERNAL, BOTH, NONE, EXISTING_INVALID, AGENT_UNAVAILABLE, CONVERT_FAILURE
}
private void baseTestImportVmFromVmwareToKvm(VcenterParameter vcenterParameter, boolean selectConvertHost,
boolean selectTemporaryStorage) throws OperationTimedoutException, AgentUnavailableException {
long clusterId = 1L;
long zoneId = 1L;
long podId = 1L;
long existingDatacenterId = 1L;
String vcenterHost = "192.168.1.2";
String datacenter = "Datacenter";
String username = "administrator@vsphere.local";
String password = "password";
String host = "192.168.1.10";
String vmName = "TestInstanceFromVmware";
instance.setName(vmName);
long newVmId = 2L;
long networkId = 1L;
when(vmDao.getNextInSequence(Long.class, "id")).thenReturn(newVmId);
ClusterVO cluster = mock(ClusterVO.class);
when(cluster.getId()).thenReturn(clusterId);
when(cluster.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(cluster.getDataCenterId()).thenReturn(zoneId);
when(clusterDao.findById(clusterId)).thenReturn(cluster);
ImportVmCmd importVmCmd = Mockito.mock(ImportVmCmd.class);
when(importVmCmd.getName()).thenReturn(vmName);
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.getNicNetworkList()).thenReturn(Map.of("NIC 1", networkId));
when(importVmCmd.getConvertInstanceHostId()).thenReturn(null);
when(importVmCmd.getConvertStoragePoolId()).thenReturn(null);
NetworkVO networkVO = Mockito.mock(NetworkVO.class);
when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2);
when(networkVO.getDataCenterId()).thenReturn(zoneId);
when(networkDao.findById(networkId)).thenReturn(networkVO);
HypervisorGuru vmwareGuru = mock(HypervisorGuru.class);
when(hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware)).thenReturn(vmwareGuru);
when(vmwareGuru.cloneHypervisorVMOutOfBand(anyString(), anyString(), anyMap())).thenReturn(instance);
when(vmwareGuru.removeClonedHypervisorVMOutOfBand(anyString(), anyString(), anyMap())).thenReturn(true);
HostVO convertHost = mock(HostVO.class);
long convertHostId = 1L;
when(convertHost.getStatus()).thenReturn(Status.Up);
when(convertHost.getResourceState()).thenReturn(ResourceState.Enabled);
when(convertHost.getId()).thenReturn(convertHostId);
when(convertHost.getName()).thenReturn("KVM-Convert-Host");
when(convertHost.getType()).thenReturn(Host.Type.Routing);
when(convertHost.getClusterId()).thenReturn(clusterId);
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);
DataStore dataStore = mock(DataStore.class);
when(dataStore.getTO()).thenReturn(dataStoreTO);
StoragePoolVO destPool = mock(StoragePoolVO.class);
when(destPool.getUuid()).thenReturn(UUID.randomUUID().toString());
when(destPool.getDataCenterId()).thenReturn(zoneId);
when(destPool.getClusterId()).thenReturn(null);
when(destPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
if (selectTemporaryStorage) {
long temporaryStoragePoolId = 1L;
when(importVmCmd.getConvertStoragePoolId()).thenReturn(temporaryStoragePoolId);
when(primaryDataStoreDao.findById(temporaryStoragePoolId)).thenReturn(destPool);
when(dataStoreManager.getPrimaryDataStore(temporaryStoragePoolId)).thenReturn(dataStore);
} else {
ImageStoreVO imageStoreVO = mock(ImageStoreVO.class);
when(imageStoreVO.getId()).thenReturn(1L);
when(imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs")).thenReturn(imageStoreVO);
when(dataStoreManager.getDataStore(1L, DataStoreRole.Image)).thenReturn(dataStore);
}
when(primaryDataStoreDao.listPoolsByCluster(clusterId)).thenReturn(List.of(destPool));
when(primaryDataStoreDao.listPoolByHostPath(Mockito.anyString(), Mockito.anyString())).thenReturn(List.of(destPool));
if (VcenterParameter.EXISTING == vcenterParameter) {
VmwareDatacenterVO datacenterVO = mock(VmwareDatacenterVO.class);
when(datacenterVO.getVcenterHost()).thenReturn(vcenterHost);
when(datacenterVO.getVmwareDatacenterName()).thenReturn(datacenter);
when(datacenterVO.getUser()).thenReturn(username);
when(datacenterVO.getPassword()).thenReturn(password);
when(importVmCmd.getExistingVcenterId()).thenReturn(existingDatacenterId);
when(vmwareDatacenterDao.findById(existingDatacenterId)).thenReturn(datacenterVO);
} else if (VcenterParameter.EXTERNAL == vcenterParameter) {
when(importVmCmd.getVcenter()).thenReturn(vcenterHost);
when(importVmCmd.getDatacenterName()).thenReturn(datacenter);
when(importVmCmd.getUsername()).thenReturn(username);
when(importVmCmd.getPassword()).thenReturn(password);
}
if (VcenterParameter.BOTH == vcenterParameter) {
when(importVmCmd.getExistingVcenterId()).thenReturn(existingDatacenterId);
when(importVmCmd.getVcenter()).thenReturn(vcenterHost);
} else if (VcenterParameter.NONE == vcenterParameter) {
when(importVmCmd.getExistingVcenterId()).thenReturn(null);
when(importVmCmd.getVcenter()).thenReturn(null);
} else if (VcenterParameter.EXISTING_INVALID == vcenterParameter) {
when(importVmCmd.getExistingVcenterId()).thenReturn(existingDatacenterId);
when(vmwareDatacenterDao.findById(existingDatacenterId)).thenReturn(null);
}
ConvertInstanceAnswer answer = mock(ConvertInstanceAnswer.class);
when(answer.getResult()).thenReturn(vcenterParameter != VcenterParameter.CONVERT_FAILURE);
when(answer.getConvertedInstance()).thenReturn(instance);
if (VcenterParameter.AGENT_UNAVAILABLE != vcenterParameter) {
when(agentManager.send(Mockito.eq(convertHostId), Mockito.any(ConvertInstanceCommand.class))).thenReturn(answer);
}
try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
unmanagedVMsManager.importVm(importVmCmd);
verify(vmwareGuru).cloneHypervisorVMOutOfBand(Mockito.eq(host), Mockito.eq(vmName), anyMap());
verify(vmwareGuru).removeClonedHypervisorVMOutOfBand(Mockito.eq(host), Mockito.eq(vmName), anyMap());
}
}
@Test
public void testImportVmFromVmwareToKvmExistingVcenter() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING, false, false);
}
@Test
public void testImportVmFromVmwareToKvmExistingVcenterSetConvertHost() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING, true, false);
}
@Test
public void testImportVmFromVmwareToKvmExistingVcenterSetConvertHostAndTemporaryStorage() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING, true, true);
}
@Test(expected = ServerApiException.class)
public void testImportVmFromVmwareToKvmExistingVcenterExclusiveParameters() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.BOTH, false, false);
}
@Test(expected = ServerApiException.class)
public void testImportVmFromVmwareToKvmExistingVcenterMissingParameters() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.NONE, false, false);
}
@Test(expected = CloudRuntimeException.class)
public void testImportVmFromVmwareToKvmExistingVcenterInvalid() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING_INVALID, false, false);
}
@Test(expected = CloudRuntimeException.class)
public void testImportVmFromVmwareToKvmExistingVcenterAgentUnavailable() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.AGENT_UNAVAILABLE, false, false);
}
@Test(expected = CloudRuntimeException.class)
public void testImportVmFromVmwareToKvmExistingVcenterConvertFailure() throws OperationTimedoutException, AgentUnavailableException {
baseTestImportVmFromVmwareToKvm(VcenterParameter.CONVERT_FAILURE, false, false);
}
private ClusterVO getClusterForTests() {
ClusterVO cluster = mock(ClusterVO.class);
when(cluster.getId()).thenReturn(1L);
when(cluster.getDataCenterId()).thenReturn(1L);
return cluster;
}
@Test(expected = CloudRuntimeException.class)
public void testSelectInstanceConversionTemporaryLocationInvalidStorage() {
ClusterVO cluster = getClusterForTests();
long poolId = 1L;
when(primaryDataStoreDao.findById(poolId)).thenReturn(null);
unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, null);
}
@Test(expected = CloudRuntimeException.class)
public void testSelectInstanceConversionTemporaryLocationPoolInvalidScope() {
ClusterVO cluster = getClusterForTests();
long poolId = 1L;
StoragePoolVO pool = mock(StoragePoolVO.class);
Mockito.when(pool.getScope()).thenReturn(ScopeType.CLUSTER);
Mockito.when(pool.getClusterId()).thenReturn(100L);
when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, null);
}
@Test(expected = CloudRuntimeException.class)
public void testSelectInstanceConversionTemporaryLocationLocalStoragePoolInvalid() {
ClusterVO cluster = getClusterForTests();
long poolId = 1L;
StoragePoolVO pool = mock(StoragePoolVO.class);
Mockito.when(pool.getScope()).thenReturn(ScopeType.HOST);
when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
HostVO convertHost = Mockito.mock(HostVO.class);
Mockito.when(convertHost.getId()).thenReturn(1L);
unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, convertHost);
}
@Test(expected = CloudRuntimeException.class)
public void testSelectInstanceConversionTemporaryLocationStoragePoolInvalidType() {
ClusterVO cluster = getClusterForTests();
long poolId = 1L;
StoragePoolVO pool = mock(StoragePoolVO.class);
Mockito.when(pool.getScope()).thenReturn(ScopeType.CLUSTER);
Mockito.when(pool.getClusterId()).thenReturn(1L);
when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
HostVO convertHost = Mockito.mock(HostVO.class);
Mockito.when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.RBD);
unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, convertHost);
}
@Test(expected = CloudRuntimeException.class)
public void testSelectInstanceConversionTemporaryLocationNoPoolAvailable() {
ClusterVO cluster = getClusterForTests();
Mockito.when(imageStoreDao.findOneByZoneAndProtocol(anyLong(), anyString())).thenReturn(null);
unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null);
}
}

View File

@ -256,6 +256,8 @@ known_categories = {
'importVsphereStoragePolicies' : 'vSphere storage policies',
'listVsphereStoragePolicies' : 'vSphere storage policies',
'ConsoleEndpoint': 'Console Endpoint',
'Shutdown': 'Shutdown',
'importVm': 'Virtual Machine',
'listQuarantinedIp': 'IP Quarantine',
'updateQuarantinedIp': 'IP Quarantine',
'removeQuarantinedIp': 'IP Quarantine',

View File

@ -326,6 +326,7 @@
"label.allocatedonly": "Allocated",
"label.allocationstate": "Allocation state",
"label.allow": "Allow",
"label.allow.duplicate.macaddresses": "Allow duplicate MAC addresses",
"label.allowuserdrivenbackups": "Allow User driven backups",
"label.annotation": "Comment",
"label.annotations": "Comments",
@ -513,6 +514,7 @@
"label.confirmdeclineinvitation": "Are you sure you want to decline this project invitation?",
"label.confirmpassword": "Confirm password",
"label.confirmpassword.description": "Please type the same password again.",
"label.connect": "Connect",
"label.connectiontimeout": "Connection timeout",
"label.conservemode": "Conserve mode",
"label.considerlasthost": "Consider Last Host",
@ -670,6 +672,7 @@
"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.importmigratefromvmwarewizard": "Import instances from VMware into a KVM cluster",
"label.desc.usage.stats": "Usage Server Statistics",
"label.description": "Description",
"label.destaddressgroupuuid": "Destination Address Group",
@ -677,6 +680,10 @@
"label.destendport": "Destination End Port",
"label.desthost": "Destination host",
"label.destination": "Destination",
"label.destination.cluster": "Destination Cluster",
"label.destination.hypervisor": "Destination Hypervisor",
"label.destination.pod": "Destination Pod",
"label.destination.zone": "Destination Zone",
"label.destinationphysicalnetworkid": "Destination physical Network ID",
"label.destinationtype": "Destination Type",
"label.destipprefix": "Destination Network Address",
@ -850,6 +857,7 @@
"label.every": "Every",
"label.example": "Example",
"label.example.plugin": "ExamplePlugin",
"label.existing": "Existing",
"label.execute": "Execute",
"label.expunge": "Expunge",
"label.expungevmgraceperiod": "Expunge Instance grace period (in sec)",
@ -857,6 +865,7 @@
"label.expunging": "Expunging",
"label.export.rules": "Export Rules",
"label.external.managed": "ExternalManaged",
"label.external": "External",
"label.external.link": "External link",
"label.externalid": "External Id",
"label.externalloadbalanceripaddress": "External load balancer IP address.",
@ -1194,6 +1203,7 @@
"label.list.nodes": "List nodes",
"label.list.pods": "List pods",
"label.list.services": "List services",
"label.list.vmware.vcenter.vms": "List VMware Instances",
"label.livepatch": "Live patch Network's router(s)",
"label.load.balancer": "Load balancer",
"label.loadbalancerinstance": "Assigned Instances",
@ -1815,6 +1825,7 @@
"label.select.project": "Select project",
"label.select.projects": "Select projects",
"label.select.ps": "Select primary storage",
"label.select.source.vcenter.datacenter": "Select the source VMware vCenter Datacenter",
"label.select.tier": "Select Network Tier",
"label.select.zones": "Select zones",
"label.select.2fa.provider": "Select the provider",
@ -1877,6 +1888,7 @@
"label.snapshottype": "Snapshot Type",
"label.sockettimeout": "Socket timeout",
"label.softwareversion": "Software version",
"label.source": "Select Import-Export Source Hypervisor",
"label.source.based": "SourceBased",
"label.sourcecidr": "Source CIDR",
"label.sourcehost": "Source host",
@ -2660,7 +2672,8 @@
"message.desc.create.ssh.key.pair": "Please fill in the following data to create or register a ssh key pair.<br><br>(1) If public key is set, CloudStack will register the public key. You can use it through your private key.<br><br>(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.<br>",
"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.<br/><br/>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. Unmanaging an Instance removes CloudStack ability to manage it. In both cases, the Instance is left running and no changes are done to the VM on the hypervisor.<br><br>For KVM, managing a VM is an experimental feature.",
"message.desc.importmigratefromvmwarewizard": "By selecting an existing or external VMware Datacenter and an instance to import, CloudStack migrates the selected instance from VMware to KVM on a conversion host using virt-v2v and imports it into a KVM cluster",
"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.<br/><br/>Provide the IP address and exported path.",
@ -2863,6 +2876,7 @@
"message.host.controlstate.retry": "Some actions on this Instance will fail, if so please wait a while and retry.",
"message.host.dedicated": "Host Dedicated",
"message.host.dedication.released": "Host dedication released.",
"message.import.running.instance.warning": "The selected VM is powered-on on the VMware Datacenter. The recommended state to convert a VMware VM into KVM is powered-off after a graceful shutdown of the guest OS.",
"message.info.cloudian.console": "Cloudian Management Console should open in another window.",
"message.installwizard.cloudstack.helptext.website": " * Project website:\t ",
"message.infra.setup.tungsten.description": "This zone must contain a Tungsten-Fabric provider because the isolation method is TF",
@ -2905,6 +2919,7 @@
"message.launch.zone.hint": "Configure Network components and traffic including IP addresses.",
"message.license.agreements.not.accepted": "License agreements not accepted.",
"message.linstor.resourcegroup.description": "Linstor resource group to use for primary storage.",
"message.list.zone.vmware.datacenter.empty": "No VMware Datacenter exists in the selected Zone",
"message.listnsp.not.return.providerid": "error: listNetworkServiceProviders API doesn't return VirtualRouter provider ID.",
"message.load.host.failed": "Failed to load hosts.",
"message.loadbalancer.stickypolicy.configuration": "Customize the load balancer stickiness policy:",

View File

@ -54,5 +54,6 @@ export default {
<style scoped lang="scss">
.tooltip-icon {
color: rgba(0,0,0,.45);
margin-left: 2px;
}
</style>

View File

@ -1773,6 +1773,9 @@ export default {
this.form.iothreadsenabled = template.details && Object.prototype.hasOwnProperty.call(template.details, 'iothreads')
this.form.iodriverpolicy = template.details?.['io.policy']
this.form.keyboard = template.details?.keyboard
if (template.details['vmware-to-kvm-mac-addresses']) {
this.dataPreFill.macAddressArray = JSON.parse(template.details['vmware-to-kvm-mac-addresses'])
}
}
} else if (name === 'isoid') {
this.templateConfigurations = []

View File

@ -104,6 +104,11 @@ export default {
minimumMemory: {
type: Number,
default: 0
},
allowAllOfferings: {
type: Boolean,
required: false,
default: false
}
},
data () {
@ -178,6 +183,9 @@ export default {
if (this.autoscale && item.iscustomized) {
disabled = true
}
if (this.allowAllOfferings) {
disabled = false
}
return {
key: item.id,
name: item.name,

View File

@ -36,7 +36,16 @@
</div>
</template>
<template v-if="column.key === 'network'">
<a-alert
v-if="hypervisor === 'KVM' && unableToMatch"
type="warning"
showIcon
banner
style="margin-bottom: 10px"
:message="$t('message.select.nic.network')"
/>
<a-select
style="width: 100%"
v-if="validNetworks[record.id] && validNetworks[record.id].length > 0"
:defaultValue="validNetworks[record.id][0].id"
@change="val => handleNetworkChange(record, val)"
@ -46,10 +55,10 @@
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="network in validNetworks[record.id]"
v-for="network in hypervisor !== 'KVM' ? validNetworks[record.id] : networks"
:key="network.id"
:label="network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '')">
{{ network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '') }}
<div>{{ network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '') }}</div>
</a-select-option>
</a-select>
<span v-else>
@ -101,6 +110,10 @@ export default {
filterMatchKey: {
type: String,
default: null
},
hypervisor: {
type: String,
default: null
}
},
data () {
@ -126,6 +139,7 @@ export default {
selectedRowKeys: [],
networks: [],
validNetworks: {},
unableToMatch: false,
values: {},
ipAddressesEnabled: {},
ipAddresses: {},
@ -201,7 +215,13 @@ export default {
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])
const filtered = this.networks.filter(x => x[this.filterMatchKey] === item[this.filterMatchKey])
if (this.hypervisor === 'KVM') {
this.unableToMatch = filtered.length === 0
this.validNetworks[item.id] = filtered.length === 0 ? this.networks : filtered.concat(this.networks.filter(x => filtered.includes(x)))
} else {
this.validNetworks[item.id] = filtered
}
}
}
this.setDefaultValues()
@ -209,6 +229,8 @@ export default {
},
setIpAddressEnabled (nic, network) {
this.ipAddressesEnabled[nic.id] = network && network.type !== 'L2'
this.ipAddresses[nic.id] = (!network || network.type === 'L2') ? null : 'auto'
this.values[nic.id] = network ? network.id : null
this.indexNum = (this.indexNum % 2) + 1
},
setIpAddress (nicId, autoAssign, ipAddress) {
@ -227,7 +249,11 @@ export default {
this.sendValuesTimed()
},
handleNetworkChange (nic, networkId) {
this.setIpAddressEnabled(nic, _.find(this.validNetworks[nic.id], (option) => option.id === networkId))
if (this.hypervisor === 'KVM') {
this.setIpAddressEnabled(nic, _.find(this.networks, (option) => option.id === networkId))
} else {
this.setIpAddressEnabled(nic, _.find(this.validNetworks[nic.id], (option) => option.id === networkId))
}
this.sendValuesTimed()
},
sendValuesTimed () {

View File

@ -188,6 +188,8 @@ export default {
const form = {}
const rules = {}
let presetMacAddressIndex = 0
this.dataItems.forEach(record => {
const ipAddressKey = 'ipAddress' + record.id
const macAddressKey = 'macAddress' + record.id
@ -202,6 +204,9 @@ export default {
rules[macAddressKey] = [{ validator: this.validatorMacAddress }]
if (record.macAddress) {
form[macAddressKey] = record.macAddress
} else if (this.preFillContent.macAddressArray && this.preFillContent.macAddressArray[presetMacAddressIndex]) {
form[macAddressKey] = this.preFillContent.macAddressArray[presetMacAddressIndex]
presetMacAddressIndex++
}
})
this.form = reactive(form)

View File

@ -17,7 +17,7 @@
<template>
<div
class="form-layout"
:class="'form-layout'"
@keyup.ctrl.enter="handleSubmit">
<span v-if="uploadPercentage > 0">
<loading-outlined />
@ -42,7 +42,7 @@
:placeholder="apiParams.url.description" />
</a-form-item>
</div>
<div v-if="currentForm === 'Upload'">
<div v-else-if="currentForm === 'Upload'">
<a-form-item :label="$t('label.templatefileupload')" name="file" ref="file">
<a-upload-dragger
:multiple="false"
@ -66,7 +66,7 @@
<a-input
v-model:value="form.name"
:placeholder="apiParams.name.description"
v-focus="currentForm !== 'Create'"/>
v-focus="currentForm === 'Upload'"/>
</a-form-item>
<a-form-item ref="displaytext" name="displaytext">
<template #label>
@ -214,7 +214,7 @@
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="allowed && (hyperKVMShow || hyperCustomShow) && currentForm !== 'Upload'">
<a-row :gutter="12" v-if="allowed && (hyperKVMShow || hyperCustomShow) && currentForm === 'Create'">
<a-col :md="24" :lg="12">
<a-form-item ref="directdownload" name="directdownload">
<template #label>
@ -429,11 +429,8 @@
</a-form-item>
</a-col>
</a-row>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</a-form>
</a-spin>
</div>
@ -674,7 +671,7 @@ export default {
if (listResponse) {
listhyperVisors = listhyperVisors.concat(listResponse)
}
if (this.currentForm !== 'Upload') {
if (this.currentForm === 'Create') {
listhyperVisors.push({
name: 'Simulator'
})
@ -1087,7 +1084,7 @@ export default {
}).finally(() => {
this.loading = false
})
} else {
} else if (this.currentForm === 'Upload') {
this.loading = true
if (this.fileList.length > 1) {
this.$notification.error({

View File

@ -34,6 +34,12 @@
:rules="rules"
@finish="handleSubmit"
layout="vertical">
<a-alert
v-if="selectedVmwareVcenter && isVmRunning"
type="warning"
:showIcon="true"
:message="$t('message.import.running.instance.warning')"
/>
<a-form-item name="displayname" ref="displayname">
<template #label>
<tooltip-label :title="$t('label.displayname')" :tooltip="apiParams.displayname.description"/>
@ -105,7 +111,7 @@
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="templateid" ref="templateid">
<a-form-item name="templateid" ref="templateid" v-if="cluster.hypervisortype === 'KVM' && !selectedVmwareVcenter">
<template #label>
<tooltip-label :title="$t('label.templatename')" :tooltip="apiParams.templateid.description + '. ' + $t('message.template.import.vm.temporary')"/>
</template>
@ -146,42 +152,83 @@
</a-row>
</a-radio-group>
</a-form-item>
<a-form-item name="converthostid" ref="converthostid">
<check-box-select-pair
layout="vertical"
v-if="cluster.hypervisortype === 'KVM' && selectedVmwareVcenter"
:resourceKey="cluster.id"
:selectOptions="kvmHostsForConversion"
:checkBoxLabel="'(Optional) Select a KVM host in the cluster to perform the instance conversion through virt-v2v'"
:defaultCheckBoxValue="false"
:reversed="false"
@handle-checkselectpair-change="updateSelectedKvmHostForConversion"
/>
</a-form-item>
<a-form-item name="convertstorageoption" ref="convertstorageoption">
<check-box-select-pair
layout="vertical"
style="margin-bottom: 20px"
v-if="cluster.hypervisortype === 'KVM' && selectedVmwareVcenter"
:resourceKey="cluster.id"
:selectOptions="storageOptionsForConversion"
:checkBoxLabel="'(Optional) Select a Storage temporary destination for the converted disks through virt-v2v'"
:defaultCheckBoxValue="false"
:reversed="false"
@handle-checkselectpair-change="updateSelectedStorageOptionForConversion"
/>
</a-form-item>
<a-form-item v-if="showStoragePoolsForConversion" name="convertstoragepool" ref="convertstoragepool" :label="$t('label.storagepool')">
<a-select
v-model:value="form.convertstoragepoolid"
defaultActiveFirstOption
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="val => { selectedStoragePoolForConversion = val }">
<a-select-option v-for="(pool) in storagePoolsForConversion" :key="pool.id" :label="pool.name">
{{ pool.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="serviceofferingid" ref="serviceofferingid">
<template #label>
<tooltip-label :title="$t('label.serviceofferingid')" :tooltip="apiParams.serviceofferingid.description"/>
</template>
<compute-offering-selection
:compute-items="computeOfferings"
:loading="computeOfferingLoading"
:rowCount="totalComputeOfferings"
:value="computeOffering ? computeOffering.id : ''"
:minimumCpunumber="isVmRunning ? resource.cpunumber : null"
:minimumCpuspeed="isVmRunning ? resource.cpuspeed : null"
:minimumMemory="isVmRunning ? resource.memory : null"
:allowAllOfferings="selectedVmwareVcenter ? true : false"
size="small"
@select-compute-item="($event) => updateComputeOffering($event)"
@handle-search-filter="($event) => fetchComputeOfferings($event)" />
<compute-selection
class="row-element"
v-if="computeOffering && (computeOffering.iscustomized || computeOffering.iscustomizediops)"
:isCustomized="computeOffering.iscustomized"
:isCustomizedIOps="'iscustomizediops' in computeOffering && computeOffering.iscustomizediops"
:cpuNumberInputDecorator="cpuNumberKey"
:cpuSpeedInputDecorator="cpuSpeedKey"
:memoryInputDecorator="memoryKey"
:computeOfferingId="computeOffering.id"
:preFillContent="resource"
:isConstrained="'serviceofferingdetails' in computeOffering"
:minCpu="getMinCpu()"
:maxCpu="getMaxCpu()"
:minMemory="getMinMemory()"
:maxMemory="getMaxMemory()"
:cpuSpeed="getCPUSpeed()"
@update-iops-value="updateFieldValue"
@update-compute-cpunumber="updateFieldValue"
@update-compute-cpuspeed="updateCpuSpeed"
@update-compute-memory="updateFieldValue" />
</a-form-item>
<compute-offering-selection
:compute-items="computeOfferings"
:loading="computeOfferingLoading"
:rowCount="totalComputeOfferings"
:value="computeOffering ? computeOffering.id : ''"
:minimumCpunumber="isVmRunning ? resource.cpunumber : null"
:minimumCpuspeed="isVmRunning ? resource.cpuspeed : null"
:minimumMemory="isVmRunning ? resource.memory : null"
size="small"
@select-compute-item="($event) => updateComputeOffering($event)"
@handle-search-filter="($event) => fetchComputeOfferings($event)" />
<compute-selection
class="row-element"
v-if="computeOffering && (computeOffering.iscustomized || computeOffering.iscustomizediops)"
:isCustomized="computeOffering.iscustomized"
:isCustomizedIOps="'iscustomizediops' in computeOffering && computeOffering.iscustomizediops"
:cpuNumberInputDecorator="cpuNumberKey"
:cpuSpeedInputDecorator="cpuSpeedKey"
:memoryInputDecorator="memoryKey"
:computeOfferingId="computeOffering.id"
:preFillContent="resource"
:isConstrained="'serviceofferingdetails' in computeOffering"
:minCpu="getMinCpu()"
:maxCpu="getMaxCpu()"
:minMemory="getMinMemory()"
:maxMemory="getMaxMemory()"
:cpuSpeed="getCPUSpeed()"
@update-iops-value="updateFieldValue"
@update-compute-cpunumber="updateFieldValue"
@update-compute-cpuspeed="updateCpuSpeed"
@update-compute-memory="updateFieldValue" />
<div v-if="resource.disk && resource.disk.length > 1">
<a-form-item name="selection" ref="selection">
<template #label>
@ -219,12 +266,25 @@
</template>
<span>{{ $t('message.ip.address.changes.effect.after.vm.restart') }}</span>
</a-form-item>
<a-row :gutter="12" justify="end">
<a-col style="text-align: right">
<a-form-item name="forced" ref="forced">
<template #label>
<tooltip-label
:title="selectedVmwareVcenter ? $t('label.allow.duplicate.macaddresses') : $t('label.forced')"
:tooltip="apiParams.forced.description"/>
</template>
<a-switch v-model:checked="form.forced" @change="val => { switches.forced = val }" />
</a-form-item>
</a-col>
</a-row>
<multi-network-selection
:items="nics"
:zoneId="cluster.zoneid"
:selectionEnabled="false"
:filterUnimplementedNetworks="true"
filterMatchKey="broadcasturi"
:hypervisor="this.cluster.hypervisortype"
@select-multi-network="updateMultiNetworkOffering" />
</div>
<a-row v-else style="margin: 12px 0">
@ -236,21 +296,13 @@
</a-row>
<a-row :gutter="12">
<a-col :md="24" :lg="12">
<a-form-item name="migrateallowed" ref="migrateallowed">
<a-form-item name="migrateallowed" ref="migrateallowed" v-if="!selectedVmwareVcenter">
<template #label>
<tooltip-label :title="$t('label.migrate.allowed')" :tooltip="apiParams.migrateallowed.description"/>
</template>
<a-switch v-model:checked="form.migrateallowed" @change="val => { switches.migrateAllowed = val }" />
</a-form-item>
</a-col>
<a-col :md="24" :lg="12">
<a-form-item name="forced" ref="forced">
<template #label>
<tooltip-label :title="$t('label.forced')" :tooltip="apiParams.forced.description"/>
</template>
<a-switch v-model:checked="form.forced" @change="val => { switches.forced = val }" />
</a-form-item>
</a-col>
</a-row>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
@ -276,6 +328,7 @@ import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection'
import MultiNetworkSelection from '@views/compute/wizard/MultiNetworkSelection'
import OsLogo from '@/components/widgets/OsLogo'
import ResourceIcon from '@/components/view/ResourceIcon'
import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
export default {
name: 'ImportUnmanagedInstances',
@ -287,13 +340,22 @@ export default {
MultiDiskSelection,
MultiNetworkSelection,
OsLogo,
ResourceIcon
ResourceIcon,
CheckBoxSelectPair
},
props: {
cluster: {
type: Object,
required: true
},
importsource: {
type: String,
required: false
},
hypervisor: {
type: String,
required: false
},
resource: {
type: Object,
required: true
@ -301,6 +363,10 @@ export default {
isOpen: {
type: Boolean,
required: false
},
selectedVmwareVcenter: {
type: Array,
required: false
}
},
data () {
@ -335,7 +401,22 @@ export default {
minIopsKey: 'minIops',
maxIopsKey: 'maxIops',
switches: {},
loading: false
loading: false,
kvmHostsForConversion: [],
selectedKvmHostForConversion: null,
storageOptionsForConversion: [
{
id: 'secondary',
name: 'Secondary Storage'
}, {
id: 'primary',
name: 'Primary Storage'
}
],
storagePoolsForConversion: [],
selectedStorageOptionForConversion: null,
selectedStoragePoolForConversion: null,
showStoragePoolsForConversion: false
}
},
beforeCreate () {
@ -455,7 +536,11 @@ export default {
nic.broadcasturi = 'pvlan://' + nic.vlanid + '-i' + nic.isolatedpvlan
}
}
nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan', networkname: 'network' })
if (this.cluster.hypervisortype === 'VMWare') {
nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan', networkname: 'network' })
} else {
nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan' })
}
nics.push(nic)
}
}
@ -496,6 +581,7 @@ export default {
pageSize: 10,
page: 1
})
this.fetchKvmHostsForConversion()
},
getMeta (obj, metaKeys) {
var meta = []
@ -670,6 +756,74 @@ export default {
}
}
},
fetchKvmHostsForConversion () {
api('listHosts', {
clusterid: this.cluster.id,
hypervisor: this.cluster.hypervisortype,
type: 'Routing',
state: 'Up',
resourcestate: 'Enabled'
}).then(json => {
this.kvmHostsForConversion = json.listhostsresponse.host || []
})
},
fetchStoragePoolsForConversion () {
if (this.selectedStorageOptionForConversion === 'primary') {
api('listStoragePools', {
zoneid: this.cluster.zoneid,
state: 'Up'
}).then(json => {
this.storagePoolsForConversion = json.liststoragepoolsresponse.storagepool || []
})
} else if (this.selectedStorageOptionForConversion === 'local') {
const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0]
api('listStoragePools', {
scope: 'HOST',
ipaddress: kvmHost.ipaddress,
state: 'Up'
}).then(json => {
this.storagePoolsForConversion = json.liststoragepoolsresponse.storagepool || []
})
}
},
updateSelectedKvmHostForConversion (clusterid, checked, value) {
if (checked) {
this.selectedKvmHostForConversion = value
const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0]
if (kvmHost.islocalstorageactive) {
this.storageOptionsForConversion.push({
id: 'local',
name: 'Host Local Storage'
})
} else {
this.resetStorageOptionsForConversion()
}
} else {
this.selectedKvmHostForConversion = null
this.resetStorageOptionsForConversion()
}
},
updateSelectedStorageOptionForConversion (clusterid, checked, value) {
if (checked) {
this.selectedStorageOptionForConversion = value
this.fetchStoragePoolsForConversion()
this.showStoragePoolsForConversion = value !== 'secondary'
} else {
this.showStoragePoolsForConversion = false
this.selectedStoragePoolForConversion = null
}
},
resetStorageOptionsForConversion () {
this.storageOptionsForConversion = [
{
id: 'secondary',
name: 'Secondary Storage'
}, {
id: 'primary',
name: 'Primary Storage'
}
]
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
@ -678,7 +832,13 @@ export default {
const params = {
name: this.resource.name,
clusterid: this.cluster.id,
displayname: values.displayname
displayname: values.displayname,
importsource: this.importsource,
hypervisor: this.hypervisor
}
var importapi = 'importUnmanagedInstance'
if (this.isExternalImport || this.isDiskImport || this.selectedVmwareVcenter) {
importapi = 'importVm'
}
if (!this.computeOffering || !this.computeOffering.id) {
this.$notification.error({
@ -722,6 +882,24 @@ export default {
})
}
}
if (this.selectedVmwareVcenter) {
if (this.selectedVmwareVcenter.existingvcenterid) {
params.existingvcenterid = this.selectedVmwareVcenter.existingvcenterid
} else {
params.vcenter = this.selectedVmwareVcenter.vcenter
params.datacentername = this.selectedVmwareVcenter.datacentername
params.username = this.selectedVmwareVcenter.username
params.password = this.selectedVmwareVcenter.password
}
params.hostip = this.resource.hostname
params.clustername = this.resource.clustername
if (this.selectedKvmHostForConversion) {
params.convertinstancehostid = this.selectedKvmHostForConversion
}
if (this.selectedStoragePoolForConversion) {
params.convertinstancepoolid = this.selectedStoragePoolForConversion
}
}
var keys = ['hostname', 'domainid', 'projectid', 'account', 'migrateallowed', 'forced']
if (this.templateType !== 'auto') {
keys.push('templateid')
@ -771,28 +949,46 @@ export default {
}
}
this.updateLoading(true)
const name = this.resource.name
api('importUnmanagedInstance', params).then(json => {
const jobId = json.importunmanagedinstanceresponse.jobid
this.$pollJob({
jobId,
title: this.$t('label.import.instance'),
description: name,
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.$emit('refresh-data')
const name = params.name
return new Promise((resolve, reject) => {
api(importapi, params).then(response => {
var jobId
if (this.isDiskImport || this.isExternalImport || this.selectedVmwareVcenter) {
jobId = response.importvmresponse.jobid
} else {
jobId = response.importunmanagedinstanceresponse.jobid
}
let msgLoading = this.$t('label.import.instance') + ' ' + name + ' ' + this.$t('label.in.progress')
if (this.selectedKvmHostForConversion) {
const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0]
msgLoading += ' on host ' + kvmHost.name
}
this.$pollJob({
jobId,
title: this.$t('label.import.instance'),
description: name,
loadingMessage: msgLoading,
catchMessage: this.$t('error.fetching.async.job.result'),
successMessage: this.$t('message.success.import.instance') + ' ' + name,
successMethod: result => {
this.$emit('refresh-data')
resolve(result)
},
errorMethod: (result) => {
this.updateLoading(false)
reject(result.jobresult.errortext)
}
})
}).catch(error => {
this.updateLoading(false)
this.$notifyError(error)
}).finally(() => {
this.closeAction()
this.updateLoading(false)
})
this.closeAction()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.updateLoading(false)
})
}).catch((error) => {
this.formRef.value.scrollToField(error.errorFields[0].name)
}).catch(() => {
this.$emit('loading-changed', false)
})
},
updateLoading (value) {

View File

@ -37,74 +37,137 @@
:md="24">
<div>
<a-card>
<a-alert type="info" :showIcon="true" :message="$t('label.desc.importexportinstancewizard')" :description="$t('message.desc.importexportinstancewizard')" />
<a-alert
type="info"
:showIcon="true"
:message="wizardTitle"
>
<template #description>
<span v-html="wizardDescription" />
</template>
</a-alert>
<br />
<a-form
style="min-width: 170px"
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
>
<a-col :md="24" :lg="8">
<a-form-item name="zoneid" ref="zoneid" :label="$t('label.zoneid')">
<a-select
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectZoneId"
:loading="optionLoading.zones"
v-focus="true"
>
<a-select-option v-for="zoneitem in zoneSelectOptions" :key="zoneitem.value" :label="zoneitem.label">
<span>
<resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ zoneitem.label }}
</span>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="24" :lg="8">
<a-form-item
name="podid"
ref="podid"
:label="$t('label.podid')">
<a-select
v-model:value="form.podid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="podSelectOptions"
:loading="optionLoading.pods"
@change="onSelectPodId"
></a-select>
</a-form-item>
</a-col>
<a-col :md="24" :lg="8">
<a-form-item
name="clusterid"
ref="clusterid"
:label="$t('label.clusterid')">
<a-select
v-model:value="form.clusterid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="clusterSelectOptions"
:loading="optionLoading.clusters"
@change="onSelectClusterId"
></a-select>
</a-form-item>
</a-col>
</a-form>
<a-divider />
<a-row :gutter="12">
<a-col :md="24" :lg="12">
<a-form
style="min-width: 170px"
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
>
<a-col :md="24" :lg="24">
<a-form-item name="sourcehypervisor" ref="sourcehypervisor" :label="$t('label.source')">
<a-radio-group
style="text-align: center; width: 100%"
v-model:value="form.sourceHypervisor"
@change="selected => { onSelectHypervisor(selected.target.value) }"
buttonStyle="solid">
<a-radio-button value="vmware" style="width: 50%; text-align: center">
VMware
</a-radio-button>
<a-radio-button value="kvm" style="width: 50%; text-align: center">
KVM
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="sourceaction" ref="sourceaction" :label="$t('label.action')" v-if="sourceActions">
<a-select
v-model:value="form.sourceAction"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectSourceAction"
:loading="optionLoading.sourcehypervisor"
v-focus="true"
>
<a-select-option v-for="opt in sourceActions" :key="opt.name" :label="opt.label">
<span>
{{ opt.label }}
</span>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-form>
</a-col>
<!-- ------------ -->
<!-- RIGHT COLUMN -->
<!-- ------------ -->
<a-col :md="24" :lg="12">
<a-form
style="min-width: 170px"
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
>
<a-form-item
name="zoneid"
ref="zoneid"
:label="isMigrateFromVmware ? $t('label.destination.zone') : $t('label.zoneid')"
>
<a-select
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectZoneId"
:loading="optionLoading.zones"
>
<a-select-option v-for="zoneitem in zoneSelectOptions" :key="zoneitem.value" :label="zoneitem.label">
<span>
<resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ zoneitem.label }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
name="podid"
ref="podid"
:label="isMigrateFromVmware ? $t('label.destination.pod') : $t('label.podid')">
<a-select
v-model:value="form.podid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="podSelectOptions"
:loading="optionLoading.pods"
@change="onSelectPodId"
></a-select>
</a-form-item>
<a-form-item
name="clusterid"
ref="clusterid"
:label="isMigrateFromVmware ? $t('label.destination.cluster') : $t('label.clusterid')">
<a-select
v-model:value="form.clusterid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="clusterSelectOptions"
:loading="optionLoading.clusters"
@change="onSelectClusterId"
></a-select>
</a-form-item>
<a-form-item v-if="isDestinationKVM && isMigrateFromVmware && clusterId != undefined">
<SelectVmwareVcenter
@loadingVmwareUnmanagedInstances="() => this.unmanagedInstancesLoading = true"
@listedVmwareUnmanagedInstances="($e) => onListUnmanagedInstancesFromVmware($e)"
/>
</a-form-item>
</a-form>
</a-col>
</a-row>
<a-divider />
<a-row :gutter="12">
<a-col :md="24" :lg="!isMigrateFromVmware ? 12 : 24">
<a-card class="instances-card">
<template #title>
{{ $t('label.unmanaged.instances') }}
@ -172,7 +235,7 @@
</div>
</a-card>
</a-col>
<a-col :md="24" :lg="12">
<a-col :md="24" :lg="12" v-if="!isMigrateFromVmware">
<a-card class="instances-card">
<template #title>
{{ $t('label.managed.instances') }}
@ -261,7 +324,10 @@
class="importform"
:resource="selectedUnmanagedInstance"
:cluster="selectedCluster"
:importsource="selectedSourceAction"
:hypervisor="this.destinationHypervisor"
:isOpen="showUnmanageForm"
:selectedVmwareVcenter="selectedVmwareVcenter"
@refresh-data="fetchInstances"
@close-action="closeImportUnmanagedInstanceForm"
@loading-changed="updateManageInstanceActionLoading"
@ -273,6 +339,7 @@
</template>
<script>
import { message } from 'ant-design-vue'
import { ref, reactive } from 'vue'
import { api } from '@/api'
import _ from 'lodash'
@ -281,6 +348,7 @@ import Status from '@/components/widgets/Status'
import SearchView from '@/components/view/SearchView'
import ImportUnmanagedInstances from '@/views/tools/ImportUnmanagedInstance'
import ResourceIcon from '@/components/view/ResourceIcon'
import SelectVmwareVcenter from '@/views/tools/SelectVmwareVcenter'
export default {
components: {
@ -288,10 +356,59 @@ export default {
Status,
SearchView,
ImportUnmanagedInstances,
ResourceIcon
ResourceIcon,
SelectVmwareVcenter
},
name: 'ManageVms',
data () {
const AllSourceActions = [
{
name: 'unmanaged',
label: 'Manage/Unmanage existing instances',
sourceDestHypervisors: {
vmware: 'vmware',
kvm: 'kvm'
},
wizardTitle: this.$t('label.desc.importexportinstancewizard'),
wizardDescription: this.$t('message.desc.importexportinstancewizard')
},
{
name: 'vmware',
label: 'Migrate existing instances to KVM',
sourceDestHypervisors: {
vmware: 'kvm'
},
wizardTitle: this.$t('label.desc.importmigratefromvmwarewizard'),
wizardDescription: this.$t('message.desc.importmigratefromvmwarewizard')
},
{
name: 'external',
label: 'Import libvirt domain from KVM Host',
sourceDestHypervisors: {
kvm: 'kvm'
},
wizardTitle: 'Import libvirt domain from KVM Host',
wizardDescription: 'Import libvirt domain from KVM Host'
},
{
name: 'local',
label: 'Import QCOW image from Local Storage',
sourceDestHypervisors: {
kvm: 'kvm'
},
wizardTitle: 'Import QCOW image from Local Storage',
wizardDescription: 'Import QCOW image from Local Storage'
},
{
name: 'shared',
label: 'Import QCOW image from Shared Storage',
sourceDestHypervisors: {
kvm: 'kvm'
},
wizardTitle: 'Import QCOW image from Shared Storage',
wizardDescription: 'Import QCOW image from Shared Storage'
}
]
const unmanagedInstancesColumns = [
{
title: this.$t('label.name'),
@ -307,6 +424,10 @@ export default {
title: this.$t('label.hostname'),
dataIndex: 'hostname'
},
{
title: this.$t('label.clustername'),
dataIndex: 'clustername'
},
{
title: this.$t('label.ostypename'),
dataIndex: 'osdisplayname'
@ -339,12 +460,15 @@ export default {
]
return {
options: {
hypervisors: [],
zones: [],
pods: [],
clusters: []
},
rowCount: {},
optionLoading: {
sourceaction: false,
hypervisors: false,
zones: false,
pods: false,
clusters: false
@ -366,15 +490,24 @@ export default {
managed: {}
},
itemCount: {},
hypervisors: [],
sourceHypervisor: 'vmware',
destinationHypervisor: 'vmware',
sourceActions: undefined,
selectedSourceAction: undefined,
wizardTitle: this.$t('label.desc.importexportinstancewizard'),
wizardDescription: this.$t('message.desc.importexportinstancewizard'),
zone: {},
zoneId: undefined,
podId: undefined,
clusterId: undefined,
listInstancesApi: {
unmanaged: 'listUnmanagedInstances',
managed: 'listVirtualMachines'
managed: 'listVirtualMachines',
migratefromvmware: 'listVmwareDcVms'
},
unmanagedInstancesColumns,
AllSourceActions,
unmanagedInstancesLoading: false,
unmanagedInstances: [],
unmanagedInstancesSelectedRowKeys: [],
@ -385,7 +518,8 @@ export default {
managedInstancesSelectedRowKeys: [],
showUnmanageForm: false,
selectedUnmanagedInstance: {},
query: {}
query: {},
selectedVmwareVcenter: undefined
}
},
created () {
@ -405,11 +539,23 @@ export default {
}
return true
},
isUnmanaged () {
return this.selectedSourceAction === 'unmanaged'
},
isUnmanagedOrExternal () {
return ((this.isUnmanaged) || this.selectedSourceAction === 'external')
},
isMigrateFromVmware () {
return this.selectedSourceAction === 'vmware'
},
isDestinationKVM () {
return this.destinationHypervisor === 'kvm'
},
params () {
return {
zones: {
list: 'listZones',
isLoad: true,
isLoad: false,
field: 'zoneid',
options: {
showicon: true
@ -428,7 +574,8 @@ export default {
isLoad: false,
options: {
zoneid: _.get(this.zone, 'id'),
podid: this.podId
podid: this.podId,
hypervisor: this.destinationHypervisor
},
field: 'clusterid'
}
@ -495,12 +642,15 @@ export default {
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({})
this.form = reactive({
sourceHypervisor: this.sourceHypervisor
})
this.rules = reactive({})
},
fetchData () {
this.unmanagedInstances = []
this.managedInstances = []
this.onSelectHypervisor(this.sourceHypervisor)
_.each(this.params, (param, name) => {
if (param.isLoad) {
this.fetchOptions(param, name)
@ -603,6 +753,24 @@ export default {
this.managedInstances = []
this.managedInstancesSelectedRowKeys = []
},
onSelectHypervisor (value) {
this.sourceHypervisor = value
this.sourceActions = this.AllSourceActions.filter(x => x.sourceDestHypervisors[value])
this.form.sourceAction = this.sourceActions[0].name || ''
this.onSelectSourceAction(this.form.sourceAction)
},
onSelectSourceAction (value) {
this.selectedSourceAction = value
const selectedAction = _.find(this.AllSourceActions, (option) => option.name === value)
this.destinationHypervisor = selectedAction.sourceDestHypervisors[this.sourceHypervisor]
this.wizardTitle = selectedAction.wizardTitle
this.wizardDescription = selectedAction.wizardDescription
this.form.zoneid = undefined
this.form.podid = undefined
this.form.clusterid = undefined
this.fetchOptions(this.params.zones, 'zones')
this.resetLists()
},
onSelectZoneId (value) {
this.zoneId = value
this.podId = null
@ -617,6 +785,7 @@ export default {
onSelectPodId (value) {
this.podId = value
this.resetLists()
this.clusterId = null
this.form.clusterid = undefined
this.updateQuery('podid', value)
this.fetchOptions(this.params.clusters, 'clusters', value)
@ -628,8 +797,8 @@ export default {
this.fetchInstances()
},
fetchInstances () {
if (this.selectedCluster.hypervisortype === 'VMware') {
this.fetchUnmanagedInstances()
this.fetchUnmanagedInstances()
if (this.isUnmanaged) {
this.fetchManagedInstances()
}
},
@ -653,12 +822,27 @@ export default {
}
this.unmanagedInstancesLoading = true
this.searchParams.unmanaged = params
api(this.listInstancesApi.unmanaged, params).then(json => {
const listUnmanagedInstances = json.listunmanagedinstancesresponse.unmanagedinstance
let apiName = this.listInstancesApi.unmanaged
if (this.isMigrateFromVmware && this.selectedVmwareVcenter) {
apiName = this.listInstancesApi.migratefromvmware
if (this.selectedVmwareVcenter.vcenter) {
params.datacentername = this.selectedVmwareVcenter.datacentername
params.vcenter = this.selectedVmwareVcenter.vcenter
params.username = this.selectedVmwareVcenter.username
params.password = this.selectedVmwareVcenter.password
} else {
params.existingvcenterid = this.selectedVmwareVcenter.existingvcenterid
}
}
api(apiName, params).then(json => {
const response = this.isMigrateFromVmware ? json.listvmwaredcvmsresponse : json.listunmanagedinstancesresponse
const listUnmanagedInstances = response.unmanagedinstance
if (this.arrayHasItems(listUnmanagedInstances)) {
this.unmanagedInstances = this.unmanagedInstances.concat(listUnmanagedInstances)
}
this.itemCount.unmanaged = json.listunmanagedinstancesresponse.count
this.itemCount.unmanaged = response.count
}).finally(() => {
this.unmanagedInstancesLoading = false
})
@ -728,7 +912,18 @@ export default {
this.selectedUnmanagedInstance.ostypename = this.selectedUnmanagedInstance.osdisplayname
this.selectedUnmanagedInstance.state = this.selectedUnmanagedInstance.powerstate
}
this.showUnmanageForm = true
if (this.isMigrateFromVmware && this.selectedUnmanagedInstance.state === 'PowerOn' && this.selectedUnmanagedInstance.ostypename.toLowerCase().includes('windows')) {
message.error({
content: () => 'Cannot import Running Windows VMs, please gracefully shutdown the source VM before importing',
style: {
marginTop: '20vh',
color: 'red'
}
})
this.showUnmanageForm = false
} else {
this.showUnmanageForm = true
}
},
closeImportUnmanagedInstanceForm () {
this.selectedUnmanagedInstance = {}
@ -780,6 +975,12 @@ export default {
this.loading = false
})
}
},
onListUnmanagedInstancesFromVmware (obj) {
this.selectedVmwareVcenter = obj.params
this.unmanagedInstances = obj.response.unmanagedinstance
this.itemCount.unmanaged = obj.response.count
this.unmanagedInstancesLoading = false
}
}
}

View File

@ -0,0 +1,273 @@
// 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.
<template>
<a-form
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
layout="vertical">
<a-col :md="24" :lg="24">
<div>
<a-form-item :label="$t('label.select.source.vcenter.datacenter')" name="vmwareopt" ref="vmwareopt">
<a-radio-group
style="text-align: center; width: 100%"
v-model:value="vcenterSelectedOption"
buttonStyle="solid">
<a-radio-button value="existing" style="width: 50%; text-align: center">
{{ $t('label.existing') }}
</a-radio-button>
<a-radio-button value="new" style="width: 50%; text-align: center">
{{ $t('label.external') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
</div>
<div v-if="vcenterSelectedOption === 'existing'">
<a-form-item name="sourcezoneid" ref="sourcezoneid" :label="$t('label.zoneid')">
<a-select
v-model:value="form.sourcezoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectZoneId"
:loading="loading"
>
<a-select-option v-for="zoneitem in zones" :key="zoneitem.id" :label="zoneitem.name">
<span>
<resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ zoneitem.name }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<div v-if="sourcezoneid">
<a-form-item :label="$t('label.vcenter')" name="vmwaredatacenter" ref="vmwaredatacenter" v-if="existingvcenter.length > 0">
<a-select
v-model:value="form.vmwaredatacenter"
:loading="loading"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:placeholder="$t('label.vcenter.datacenter')"
@change="onSelectExistingVmwareDatacenter">
<a-select-option v-for="opt in existingvcenter" :key="opt.id">
{{ 'VC: ' + opt.vcenter + ' - DC: ' + opt.name }}
</a-select-option>
</a-select>
</a-form-item>
<div v-else>
{{ $t('message.list.zone.vmware.datacenter.empty') }}
</div>
</div>
</div>
<div v-else-if="vcenterSelectedOption === 'new'">
<a-form-item ref="vcenter" name="vcenter">
<template #label>
<tooltip-label :title="$t('label.vcenter')" :tooltip="apiParams.vcenter.description"/>
</template>
<a-input
v-model:value="vcenter"
:placeholder="apiParams.vcenter.description"
/>
</a-form-item>
<a-form-item ref="datacenter" name="datacenter">
<template #label>
<tooltip-label :title="$t('label.vcenter.datacenter')" :tooltip="apiParams.datacentername.description"/>
</template>
<a-input
v-model:value="datacenter"
:placeholder="apiParams.datacentername.description"
/>
</a-form-item>
<a-form-item ref="username" name="username">
<template #label>
<tooltip-label :title="$t('label.vcenter.username')" :tooltip="apiParams.username.description"/>
</template>
<a-input
v-model:value="username"
:placeholder="apiParams.username.description"
/>
</a-form-item>
<a-form-item ref="password" name="password">
<template #label>
<tooltip-label :title="$t('label.vcenter.password')" :tooltip="apiParams.password.description"/>
</template>
<a-input-password
v-model:value="password"
:placeholder="apiParams.password.description"
/>
</a-form-item>
</div>
<div class="card-footer">
<a-button
v-if="vcenterSelectedOption == 'existing' || vcenterSelectedOption == 'new'"
:disabled="(vcenterSelectedOption === 'new' && (vcenter === '' || datacentername === '' || username === '' || password === '')) ||
(vcenterSelectedOption === 'existing' && selectedExistingVcenterId === '')"
:loading="loading"
type="primary"
@click="listVmwareDatacenterVms">{{ $t('label.list.vmware.vcenter.vms') }}</a-button>
</div>
</a-col>
</a-form>
</template>
<script>
import { api } from '@/api'
import { ref, reactive } from 'vue'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import Status from '@/components/widgets/Status'
export default {
name: 'SelectVmwareVcenter',
components: {
TooltipLabel,
Status
},
data () {
return {
vcenter: '',
datacenter: '',
username: '',
password: '',
loading: false,
zones: {},
vcenterSelectedOption: '',
existingvcenter: [],
selectedExistingVcenterId: '',
selectedPoweredOnVm: false,
vmwareDcVms: [],
vmwareDcVmSelectedRows: [],
vmwareDcVmsColumns: [
{
title: this.$t('label.hostname'),
dataIndex: 'hostname'
},
{
title: this.$t('label.cluster'),
dataIndex: 'clustername'
},
{
title: this.$t('label.virtualmachinename'),
dataIndex: 'virtualmachinename'
},
{
title: this.$t('label.powerstate'),
key: 'powerstate',
dataIndex: 'powerstate'
}
]
}
},
computed: {
vmwareDcVmsSelection () {
return {
type: 'radio',
selectedRowKeys: this.vmwareDcVmSelectedRows || [],
onChange: this.onVmwareDcVmSelectRow
}
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('listVmwareDcVms')
},
created () {
this.initForm()
this.fetchZones()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
vcenter: '',
username: '',
password: ''
})
this.rules = reactive({})
},
listVmwareDatacenterVms () {
this.loading = true
this.$emit('loadingVmwareUnmanagedInstances')
const params = {}
if (this.vcenterSelectedOption === 'new') {
params.datacentername = this.datacenter
params.vcenter = this.vcenter
params.username = this.username
params.password = this.password
} else {
params.existingvcenterid = this.selectedExistingVcenterId
}
api('listVmwareDcVms', params).then(json => {
const obj = {
params: params,
response: json.listvmwaredcvmsresponse
}
this.$emit('listedVmwareUnmanagedInstances', obj)
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
},
fetchZones () {
this.loading = true
api('listZones', { showicon: true }).then(response => {
this.zones = response.listzonesresponse.zone || []
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
},
onSelectZoneId (value) {
this.sourcezoneid = value
this.listZoneVmwareDcs()
},
listZoneVmwareDcs () {
this.loading = true
api('listVmwareDcs', { zoneid: this.sourcezoneid }).then(response => {
if (response.listvmwaredcsresponse.VMwareDC && response.listvmwaredcsresponse.VMwareDC.length > 0) {
this.existingvcenter = response.listvmwaredcsresponse.VMwareDC
}
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
},
onSelectExistingVmwareDatacenter (value) {
this.selectedExistingVcenterId = value
}
}
}
</script>
<style scoped>
.card-footer {
text-align: right;
}
.card-footer button {
width: 50%;
text-align: center;
}
</style>

View File

@ -22,6 +22,7 @@ package com.cloud.utils.script;
import java.io.BufferedReader;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
/**
@ -138,4 +139,31 @@ public abstract class OutputInterpreter {
}
}
public static class LineByLineOutputLogger extends OutputInterpreter {
private Logger logger;
private String logPrefix;
public LineByLineOutputLogger(Logger logger) {
this.logger = logger;
}
public LineByLineOutputLogger(Logger logger, String logPrefix) {
this.logger = logger;
this.logPrefix = logPrefix;
}
@Override
public boolean drain() {
return true;
}
@Override
public String interpret(BufferedReader reader) throws IOException {
String line;
while ((line = reader.readLine()) != null) {
logger.info(StringUtils.isNotBlank(logPrefix) ? String.format("(%s) %s", logPrefix, line) : line);
}
return null;
}
}
}

View File

@ -56,6 +56,7 @@ public class Script implements Callable<String> {
private volatile boolean _isTimeOut = false;
private boolean _passwordCommand = false;
private boolean avoidLoggingCommand = false;
private static final ScheduledExecutorService s_executors = Executors.newScheduledThreadPool(10, new NamedThreadFactory("Script"));
@ -73,6 +74,10 @@ public class Script implements Callable<String> {
return _process.exitValue();
}
public void setAvoidLoggingCommand(boolean avoid) {
avoidLoggingCommand = avoid;
}
public Script(String command, Duration timeout, Logger logger) {
this(command, timeout.getMillis(), logger);
}
@ -204,9 +209,10 @@ public class Script implements Callable<String> {
public String execute(OutputInterpreter interpreter) {
String[] command = _command.toArray(new String[_command.size()]);
String commandLine = buildCommandLine(command);
_logger.debug(String.format("Executing command [%s].", commandLine.split(KeyStoreUtils.KS_FILENAME)[0]));
if (_logger.isDebugEnabled() && !avoidLoggingCommand) {
_logger.debug(String.format("Executing command [%s].", commandLine.split(KeyStoreUtils.KS_FILENAME)[0]));
}
try {
_logger.trace(String.format("Creating process for command [%s].", commandLine));
@ -518,14 +524,19 @@ public class Script implements Callable<String> {
}
public static int runSimpleBashScriptForExitValue(String command) {
return runSimpleBashScriptForExitValue(command, 0);
return runSimpleBashScriptForExitValue(command, 0, false);
}
public static int runSimpleBashScriptForExitValue(String command, int timeout) {
public static int runSimpleBashScriptForExitValueAvoidLogging(String command) {
return runSimpleBashScriptForExitValue(command, 0, true);
}
public static int runSimpleBashScriptForExitValue(String command, int timeout, boolean avoidLogging) {
Script s = new Script("/bin/bash", timeout);
s.add("-c");
s.add(command);
s.setAvoidLoggingCommand(avoidLogging);
String result = s.execute(null);
if (result == null || result.trim().isEmpty())

View File

@ -78,5 +78,11 @@
<artifactId>maven-artifact</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-core</artifactId>
<version>4.19.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -21,6 +21,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.cloud.hypervisor.vmware.util.VmwareHelper;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
@ -159,14 +161,24 @@ public class DatacenterMO extends BaseMO {
return null;
}
public List<Pair<ManagedObjectReference, String>> getAllVmsOnDatacenter() throws Exception {
List<Pair<ManagedObjectReference, String>> vms = new ArrayList<Pair<ManagedObjectReference, String>>();
public List<UnmanagedInstanceTO> getAllVmsOnDatacenter() throws Exception {
List<UnmanagedInstanceTO> vms = new ArrayList<>();
List<ObjectContent> ocs = getVmPropertiesOnDatacenterVmFolder(new String[] {"name"});
if (ocs != null) {
for (ObjectContent oc : ocs) {
String vmName = oc.getPropSet().get(0).getVal().toString();
vms.add(new Pair<ManagedObjectReference, String>(oc.getObj(), vmName));
ManagedObjectReference vmMor = oc.getObj();
if (vmMor != null) {
VirtualMachineMO vmMo = new VirtualMachineMO(_context, vmMor);
try {
if (!vmMo.isTemplate()) {
HostMO hostMO = vmMo.getRunningHost();
UnmanagedInstanceTO unmanagedInstance = VmwareHelper.getUnmanagedInstance(hostMO, vmMo);
vms.add(unmanagedInstance);
}
} catch (Exception e) {
s_logger.debug(String.format("Unexpected error checking unmanaged instance %s, excluding it: %s", vmMo.getVmName(), e.getMessage()), e);
}
}
}
}

View File

@ -26,8 +26,11 @@ import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.UUID;
@ -37,6 +40,30 @@ import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import com.cloud.hypervisor.vmware.mo.ClusterMO;
import com.cloud.hypervisor.vmware.mo.DatastoreFile;
import com.cloud.hypervisor.vmware.mo.DistributedVirtualSwitchMO;
import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper;
import com.cloud.serializer.GsonHelper;
import com.cloud.utils.net.NetUtils;
import com.vmware.vim25.DatastoreInfo;
import com.vmware.vim25.DistributedVirtualPort;
import com.vmware.vim25.DistributedVirtualSwitchPortCriteria;
import com.vmware.vim25.GuestInfo;
import com.vmware.vim25.GuestNicInfo;
import com.vmware.vim25.HostPortGroupSpec;
import com.vmware.vim25.NasDatastoreInfo;
import com.vmware.vim25.VMwareDVSPortSetting;
import com.vmware.vim25.VirtualDeviceFileBackingInfo;
import com.vmware.vim25.VirtualIDEController;
import com.vmware.vim25.VirtualMachineGuestOsIdentifier;
import com.vmware.vim25.VirtualMachineToolsStatus;
import com.vmware.vim25.VirtualSCSIController;
import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec;
import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec;
import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
@ -763,4 +790,268 @@ public class VmwareHelper {
}
return host;
}
public static UnmanagedInstanceTO getUnmanagedInstance(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
UnmanagedInstanceTO instance = null;
try {
instance = new UnmanagedInstanceTO();
instance.setName(vmMo.getVmName());
instance.setInternalCSName(vmMo.getInternalCSName());
instance.setCpuCores(vmMo.getConfigSummary().getNumCpu());
instance.setCpuCoresPerSocket(vmMo.getCoresPerSocket());
instance.setCpuSpeed(vmMo.getConfigSummary().getCpuReservation());
instance.setMemory(vmMo.getConfigSummary().getMemorySizeMB());
instance.setOperatingSystemId(vmMo.getVmGuestInfo().getGuestId());
ClusterMO clusterMo = new ClusterMO(hyperHost.getContext(), hyperHost.getHyperHostCluster());
instance.setClusterName(clusterMo.getName());
instance.setHostName(hyperHost.getHyperHostName());
if (StringUtils.isEmpty(instance.getOperatingSystemId())) {
instance.setOperatingSystemId(vmMo.getConfigSummary().getGuestId());
}
VirtualMachineGuestOsIdentifier osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST;
try {
osIdentifier = VirtualMachineGuestOsIdentifier.fromValue(instance.getOperatingSystemId());
} catch (IllegalArgumentException iae) {
if (StringUtils.isNotEmpty(instance.getOperatingSystemId()) && instance.getOperatingSystemId().contains("64")) {
osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST_64;
}
}
instance.setOperatingSystem(vmMo.getGuestInfo().getGuestFullName());
if (StringUtils.isEmpty(instance.getOperatingSystem())) {
instance.setOperatingSystem(vmMo.getConfigSummary().getGuestFullName());
}
UnmanagedInstanceTO.PowerState powerState = UnmanagedInstanceTO.PowerState.PowerUnknown;
if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_ON")) {
powerState = UnmanagedInstanceTO.PowerState.PowerOn;
instance.setCpuSpeed(vmMo.getRuntimeInfo().getMaxCpuUsage() / instance.getCpuCores());
}
if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_OFF")) {
powerState = UnmanagedInstanceTO.PowerState.PowerOff;
}
instance.setPowerState(powerState);
instance.setDisks(getUnmanageInstanceDisks(vmMo));
instance.setNics(getUnmanageInstanceNics(hyperHost, vmMo));
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance info. " + e.getMessage());
}
return instance;
}
protected static List<UnmanagedInstanceTO.Disk> getUnmanageInstanceDisks(VirtualMachineMO vmMo) {
List<UnmanagedInstanceTO.Disk> instanceDisks = new ArrayList<>();
VirtualDisk[] disks = null;
try {
disks = vmMo.getAllDiskDevice();
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance disks. " + e.getMessage());
}
if (disks != null) {
for (VirtualDevice diskDevice : disks) {
try {
if (diskDevice instanceof VirtualDisk) {
UnmanagedInstanceTO.Disk instanceDisk = new UnmanagedInstanceTO.Disk();
VirtualDisk disk = (VirtualDisk) diskDevice;
instanceDisk.setDiskId(disk.getDiskObjectId());
instanceDisk.setLabel(disk.getDeviceInfo() != null ? disk.getDeviceInfo().getLabel() : "");
instanceDisk.setFileBaseName(vmMo.getVmdkFileBaseName(disk));
instanceDisk.setImagePath(getAbsoluteVmdkFile(disk));
instanceDisk.setCapacity(disk.getCapacityInBytes());
instanceDisk.setPosition(diskDevice.getUnitNumber());
DatastoreFile file = new DatastoreFile(getAbsoluteVmdkFile(disk));
if (StringUtils.isNoneEmpty(file.getFileBaseName(), file.getDatastoreName())) {
VirtualMachineDiskInfo diskInfo = vmMo.getDiskInfoBuilder().getDiskInfoByBackingFileBaseName(file.getFileBaseName(), file.getDatastoreName());
instanceDisk.setChainInfo(GsonHelper.getGsonLogger().toJson(diskInfo));
}
for (VirtualDevice device : vmMo.getAllDeviceList()) {
if (diskDevice.getControllerKey() == device.getKey()) {
if (device instanceof VirtualIDEController) {
instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
instanceDisk.setControllerUnit(((VirtualIDEController) device).getBusNumber());
} else if (device instanceof VirtualSCSIController) {
instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
instanceDisk.setControllerUnit(((VirtualSCSIController) device).getBusNumber());
} else {
instanceDisk.setController(DiskControllerType.none.toString());
}
break;
}
}
if (disk.getBacking() instanceof VirtualDeviceFileBackingInfo) {
VirtualDeviceFileBackingInfo diskBacking = (VirtualDeviceFileBackingInfo) disk.getBacking();
ManagedObjectReference morDs = diskBacking.getDatastore();
DatastoreInfo info = (DatastoreInfo)vmMo.getContext().getVimClient().getDynamicProperty(diskBacking.getDatastore(), "info");
if (info instanceof NasDatastoreInfo) {
NasDatastoreInfo dsInfo = (NasDatastoreInfo) info;
instanceDisk.setDatastoreName(dsInfo.getName());
if (dsInfo.getNas() != null) {
instanceDisk.setDatastoreHost(dsInfo.getNas().getRemoteHost());
instanceDisk.setDatastorePath(dsInfo.getNas().getRemotePath());
instanceDisk.setDatastoreType(dsInfo.getNas().getType());
}
} else {
instanceDisk.setDatastoreName(info.getName());
}
}
s_logger.info(vmMo.getName() + " " + disk.getDeviceInfo().getLabel() + " " + disk.getDeviceInfo().getSummary() + " " + disk.getDiskObjectId() + " " + disk.getCapacityInKB() + " " + instanceDisk.getController());
instanceDisks.add(instanceDisk);
}
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance disk info. " + e.getMessage());
}
}
Collections.sort(instanceDisks, new Comparator<UnmanagedInstanceTO.Disk>() {
@Override
public int compare(final UnmanagedInstanceTO.Disk disk1, final UnmanagedInstanceTO.Disk disk2) {
return extractInt(disk1) - extractInt(disk2);
}
int extractInt(UnmanagedInstanceTO.Disk disk) {
String num = disk.getLabel().replaceAll("\\D", "");
// return 0 if no digits found
return num.isEmpty() ? 0 : Integer.parseInt(num);
}
});
}
return instanceDisks;
}
private static List<UnmanagedInstanceTO.Nic> getUnmanageInstanceNics(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
List<UnmanagedInstanceTO.Nic> instanceNics = new ArrayList<>();
HashMap<String, List<String>> guestNicMacIPAddressMap = new HashMap<>();
try {
GuestInfo guestInfo = vmMo.getGuestInfo();
if (guestInfo.getToolsStatus() == VirtualMachineToolsStatus.TOOLS_OK) {
for (GuestNicInfo nicInfo: guestInfo.getNet()) {
if (CollectionUtils.isNotEmpty(nicInfo.getIpAddress())) {
List<String> ipAddresses = new ArrayList<>();
for (String ipAddress : nicInfo.getIpAddress()) {
if (NetUtils.isValidIp4(ipAddress)) {
ipAddresses.add(ipAddress);
}
}
guestNicMacIPAddressMap.put(nicInfo.getMacAddress(), ipAddresses);
}
}
} else {
s_logger.info(String.format("Unable to retrieve guest nics for instance: %s from VMware tools as tools status: %s", vmMo.getName(), guestInfo.getToolsStatus().toString()));
}
} catch (Exception e) {
s_logger.info("Unable to retrieve guest nics for instance from VMware tools. " + e.getMessage());
}
VirtualDevice[] nics = null;
try {
nics = vmMo.getNicDevices();
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance nics. " + e.getMessage());
}
if (nics != null) {
for (VirtualDevice nic : nics) {
try {
VirtualEthernetCard ethCardDevice = (VirtualEthernetCard) nic;
s_logger.error(nic.getClass().getCanonicalName() + " " + nic.getBacking().getClass().getCanonicalName() + " " + ethCardDevice.getMacAddress());
UnmanagedInstanceTO.Nic instanceNic = new UnmanagedInstanceTO.Nic();
instanceNic.setNicId(ethCardDevice.getDeviceInfo().getLabel());
if (ethCardDevice instanceof VirtualPCNet32) {
instanceNic.setAdapterType(VirtualEthernetCardType.PCNet32.toString());
} else if (ethCardDevice instanceof VirtualVmxnet2) {
instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet2.toString());
} else if (ethCardDevice instanceof VirtualVmxnet3) {
instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet3.toString());
} else {
instanceNic.setAdapterType(VirtualEthernetCardType.E1000.toString());
}
instanceNic.setMacAddress(ethCardDevice.getMacAddress());
if (guestNicMacIPAddressMap.containsKey(instanceNic.getMacAddress())) {
instanceNic.setIpAddress(guestNicMacIPAddressMap.get(instanceNic.getMacAddress()));
}
if (ethCardDevice.getSlotInfo() != null) {
instanceNic.setPciSlot(ethCardDevice.getSlotInfo().toString());
}
VirtualDeviceBackingInfo backing = ethCardDevice.getBacking();
if (backing instanceof VirtualEthernetCardDistributedVirtualPortBackingInfo) {
VirtualEthernetCardDistributedVirtualPortBackingInfo backingInfo = (VirtualEthernetCardDistributedVirtualPortBackingInfo) backing;
DistributedVirtualSwitchPortConnection port = backingInfo.getPort();
String portKey = port.getPortKey();
String portGroupKey = port.getPortgroupKey();
String dvSwitchUuid = port.getSwitchUuid();
s_logger.debug("NIC " + nic.toString() + " is connected to dvSwitch " + dvSwitchUuid + " pg " + portGroupKey + " port " + portKey);
ManagedObjectReference dvSwitchManager = vmMo.getContext().getVimClient().getServiceContent().getDvSwitchManager();
ManagedObjectReference dvSwitch = vmMo.getContext().getVimClient().getService().queryDvsByUuid(dvSwitchManager, dvSwitchUuid);
// Get all ports
DistributedVirtualSwitchPortCriteria criteria = new DistributedVirtualSwitchPortCriteria();
criteria.setInside(true);
criteria.getPortgroupKey().add(portGroupKey);
List<DistributedVirtualPort> dvPorts = vmMo.getContext().getVimClient().getService().fetchDVPorts(dvSwitch, criteria);
for (DistributedVirtualPort dvPort : dvPorts) {
// Find the port for this NIC by portkey
if (portKey.equals(dvPort.getKey())) {
VMwareDVSPortSetting settings = (VMwareDVSPortSetting) dvPort.getConfig().getSetting();
if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchVlanIdSpec) {
VmwareDistributedVirtualSwitchVlanIdSpec vlanId = (VmwareDistributedVirtualSwitchVlanIdSpec) settings.getVlan();
s_logger.trace("Found port " + dvPort.getKey() + " with vlan " + vlanId.getVlanId());
if (vlanId.getVlanId() > 0 && vlanId.getVlanId() < 4095) {
instanceNic.setVlan(vlanId.getVlanId());
}
} else if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchPvlanSpec) {
VmwareDistributedVirtualSwitchPvlanSpec pvlanSpec = (VmwareDistributedVirtualSwitchPvlanSpec) settings.getVlan();
s_logger.trace("Found port " + dvPort.getKey() + " with pvlan " + pvlanSpec.getPvlanId());
if (pvlanSpec.getPvlanId() > 0 && pvlanSpec.getPvlanId() < 4095) {
DistributedVirtualSwitchMO dvSwitchMo = new DistributedVirtualSwitchMO(vmMo.getContext(), dvSwitch);
Pair<Integer, HypervisorHostHelper.PvlanType> vlanDetails = dvSwitchMo.retrieveVlanFromPvlan(pvlanSpec.getPvlanId(), dvSwitch);
if (vlanDetails != null && vlanDetails.first() != null && vlanDetails.second() != null) {
instanceNic.setVlan(vlanDetails.first());
instanceNic.setPvlan(pvlanSpec.getPvlanId());
instanceNic.setPvlanType(vlanDetails.second().toString());
}
}
}
break;
}
}
} else if (backing instanceof VirtualEthernetCardNetworkBackingInfo) {
VirtualEthernetCardNetworkBackingInfo backingInfo = (VirtualEthernetCardNetworkBackingInfo) backing;
instanceNic.setNetwork(backingInfo.getDeviceName());
if (hyperHost instanceof HostMO) {
HostMO hostMo = (HostMO) hyperHost;
HostPortGroupSpec portGroupSpec = hostMo.getHostPortGroupSpec(backingInfo.getDeviceName());
instanceNic.setVlan(portGroupSpec.getVlanId());
}
}
instanceNics.add(instanceNic);
} catch (Exception e) {
s_logger.info("Unable to retrieve unmanaged instance nic info. " + e.getMessage());
}
}
Collections.sort(instanceNics, new Comparator<UnmanagedInstanceTO.Nic>() {
@Override
public int compare(final UnmanagedInstanceTO.Nic nic1, final UnmanagedInstanceTO.Nic nic2) {
return extractInt(nic1) - extractInt(nic2);
}
int extractInt(UnmanagedInstanceTO.Nic nic) {
String num = nic.getNicId().replaceAll("\\D", "");
// return 0 if no digits found
return num.isEmpty() ? 0 : Integer.parseInt(num);
}
});
}
return instanceNics;
}
public static String getAbsoluteVmdkFile(VirtualDisk disk) {
String vmdkAbsFile = null;
VirtualDeviceBackingInfo backingInfo = disk.getBacking();
if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;
vmdkAbsFile = diskBackingInfo.getFileName();
}
return vmdkAbsFile;
}
}

View File

@ -20,6 +20,13 @@ package com.cloud.hypervisor.vmware.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import com.vmware.vim25.DatastoreInfo;
import com.vmware.vim25.Description;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@ -29,11 +36,45 @@ import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.hypervisor.vmware.mo.VirtualMachineMO;
import com.vmware.vim25.VirtualDisk;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class VmwareHelperTest {
@Mock
private VirtualMachineMO virtualMachineMO;
private static final String diskLabel = "disk1";
private static final String diskFileBaseName = "xyz.vmdk";
private static final String dataStoreName = "Datastore";
private static final String vmName = "VM1";
@Before
public void setUp() throws Exception {
VirtualDiskFlatVer2BackingInfo backingInfo = Mockito.mock(VirtualDiskFlatVer2BackingInfo.class);
Mockito.when(backingInfo.getFileName()).thenReturn("abc");
Mockito.when(backingInfo.getDatastore()).thenReturn(Mockito.mock(ManagedObjectReference.class));
VirtualDisk disk = Mockito.mock(VirtualDisk.class);
VirtualDisk[] disks = new VirtualDisk[1];
disks[0] = disk;
Description description = Mockito.mock(Description.class);
Mockito.when(description.getLabel()).thenReturn(diskLabel);
Mockito.when(description.getSummary()).thenReturn("");
Mockito.when(disk.getBacking()).thenReturn(backingInfo);
Mockito.when(disk.getDeviceInfo()).thenReturn(description);
Mockito.when(virtualMachineMO.getAllDiskDevice()).thenReturn(disks);
Mockito.when(virtualMachineMO.getVmdkFileBaseName(disk)).thenReturn(diskFileBaseName);
DatastoreInfo datastoreInfo = Mockito.mock(DatastoreInfo.class);
Mockito.when(datastoreInfo.getName()).thenReturn(dataStoreName);
VmwareClient client = Mockito.mock(VmwareClient.class);
Mockito.when(client.getDynamicProperty(Mockito.any(ManagedObjectReference.class), Mockito.anyString()))
.thenReturn(datastoreInfo);
VmwareContext context = Mockito.mock(VmwareContext.class);
Mockito.when(context.getVimClient()).thenReturn(client);
Mockito.when(virtualMachineMO.getContext()).thenReturn(context);
Mockito.when(virtualMachineMO.getName()).thenReturn(vmName);
}
@Test
public void prepareDiskDeviceTestNotLimitingIOPS() throws Exception {
Mockito.when(virtualMachineMO.getIDEDeviceControllerKey()).thenReturn(1);
@ -54,4 +95,14 @@ public class VmwareHelperTest {
VirtualDisk virtualDisk = (VirtualDisk) VmwareHelper.prepareDiskDevice(virtualMachineMO, null, -1, new String[1], null, 0, 0, Long.valueOf(0));
assertNull(virtualDisk.getStorageIOAllocation());
}
@Test
public void testGetUnmanageInstanceDisks() {
List<UnmanagedInstanceTO.Disk> disks = VmwareHelper.getUnmanageInstanceDisks(virtualMachineMO);
Assert.assertEquals(1, disks.size());
UnmanagedInstanceTO.Disk disk = disks.get(0);
Assert.assertEquals(diskLabel, disk.getLabel());
Assert.assertEquals(diskFileBaseName, disk.getFileBaseName());
Assert.assertEquals(dataStoreName, disk.getDatastoreName());
}
}