[VMware] Enable unmanaging guest VMs (#4103)

* Enable unmanaging guest VMs

* Minor fixes

* Fix stop usage event only if VM is not stopped when unmanaging

* Rename unmanaged VMs manager

* Generate netofferingremove usage event if VM is not stopped

* Generate usage event VM snapshot primary off when unmanaging
This commit is contained in:
Nicolas Vazquez 2020-06-26 08:31:43 -03:00 committed by GitHub
parent 3ede1eaa49
commit 8c1d749360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1174 additions and 40 deletions

View File

@ -102,6 +102,7 @@ public class EventTypes {
public static final String EVENT_VM_RESTORE = "VM.RESTORE";
public static final String EVENT_VM_EXPUNGE = "VM.EXPUNGE";
public static final String EVENT_VM_IMPORT = "VM.IMPORT";
public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE";
// Domain Router
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
@ -624,6 +625,7 @@ public class EventTypes {
entityEventDetails.put(EVENT_VM_RESTORE, VirtualMachine.class);
entityEventDetails.put(EVENT_VM_EXPUNGE, VirtualMachine.class);
entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class);
entityEventDetails.put(EVENT_VM_UNMANAGE, VirtualMachine.class);
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);

View File

@ -517,4 +517,9 @@ public interface UserVmService {
final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKey,
final String hostName, final HypervisorType hypervisorType, final Map<String, String> customParameters, final VirtualMachine.PowerState powerState) throws InsufficientCapacityException;
/**
* Unmanage a guest VM from CloudStack
* @return true if the VM is successfully unmanaged, false if not.
*/
boolean unmanageUserVM(Long vmId);
}

View File

@ -64,6 +64,7 @@ public interface VirtualMachineProfile {
public static final Param BootMode = new Param("BootMode");
public static final Param BootType = new Param("BootType");
public static final Param BootIntoSetup = new Param("enterHardwareSetup");
public static final Param PreserveNics = new Param("PreserveNics");
private String name;

View File

@ -52,5 +52,5 @@ public interface VMSnapshotService {
* the vm gets deleted on hypervisor (no need to delete each vm snapshot before deleting vm, just mark them as deleted on DB)
* @param id vm id
*/
boolean deleteVMSnapshotsFromDB(Long vmId);
boolean deleteVMSnapshotsFromDB(Long vmId, boolean unmanage);
}

View File

@ -39,6 +39,7 @@ import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.VmImportService;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.event.EventTypes;
@ -152,6 +153,11 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
description = "vm and its volumes are allowed to migrate to different host/pool when offerings passed are incompatible with current host/pool")
private Boolean migrateAllowed;
@Parameter(name = ApiConstants.FORCED,
type = CommandType.BOOLEAN,
description = "VM is imported despite some of its NIC's MAC addresses are already present")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -268,6 +274,10 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
return "Importing unmanaged VM";
}
public boolean isForced() {
return BooleanUtils.isTrue(forced);
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -0,0 +1,136 @@
//
// 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 com.cloud.user.Account;
import com.cloud.uservm.UserVm;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandJobType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.UnmanageVMInstanceResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.UnmanagedVMsManager;
import org.apache.log4j.Logger;
import javax.inject.Inject;
@APICommand(name = UnmanageVMInstanceCmd.API_NAME,
description = "Unmanage a guest virtual machine.",
entityType = {VirtualMachine.class},
responseObject = UnmanageVMInstanceResponse.class,
requestHasSensitiveInfo = false,
authorized = {RoleType.Admin},
since = "4.15.0")
public class UnmanageVMInstanceCmd extends BaseAsyncCmd {
public static final Logger LOGGER = Logger.getLogger(UnmanageVMInstanceCmd.class);
public static final String API_NAME = "unmanageVirtualMachine";
@Inject
private UnmanagedVMsManager unmanagedVMsManager;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = UserVmResponse.class, required = true,
description = "The ID of the virtual machine to unmanage")
private Long vmId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getVmId() {
return vmId;
}
@Override
public String getEventType() {
return EventTypes.EVENT_VM_UNMANAGE;
}
@Override
public String getEventDescription() {
return "unmanaging VM. VM ID = " + vmId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
UnmanageVMInstanceResponse response = new UnmanageVMInstanceResponse();
try {
CallContext.current().setEventDetails("VM ID = " + vmId);
boolean result = unmanagedVMsManager.unmanageVMInstance(vmId);
response.setSuccess(result);
if (result) {
response.setDetails("VM unmanaged successfully");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
response.setResponseName(getCommandName());
response.setObjectName(getCommandName());
this.setResponseObject(response);
}
@Override
public String getCommandName() {
return API_NAME.toLowerCase() + BaseAsyncCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
UserVm vm = _responseGenerator.findUserVmById(vmId);
if (vm != null) {
return vm.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public ApiCommandJobType getInstanceType() {
return ApiCommandJobType.VirtualMachine;
}
@Override
public Long getInstanceId() {
return vmId;
}
}

View File

@ -0,0 +1,58 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
public class UnmanageVMInstanceResponse extends BaseResponse {
@SerializedName(ApiConstants.RESULT)
@Param(description = "result of the unmanage VM operation")
private boolean success;
@SerializedName(ApiConstants.DETAILS)
@Param(description = "details of the unmanage VM operation")
private String details;
public UnmanageVMInstanceResponse() {
}
public UnmanageVMInstanceResponse(boolean success, String details) {
this.success = success;
this.details = details;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}

View File

@ -0,0 +1,27 @@
// 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.vm;
public interface UnmanageVMService {
/**
* Unmanage a guest VM from CloudStack
* @return true if the VM is successfully unmanaged, false if not.
*/
boolean unmanageVMInstance(long vmId);
}

View File

@ -0,0 +1,29 @@
// 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.vm;
import com.cloud.utils.component.PluggableService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
public interface UnmanagedVMsManager extends VmImportService, UnmanageVMService, PluggableService, Configurable {
ConfigKey<Boolean> UnmanageVMPreserveNic = new ConfigKey<>("Advanced", Boolean.class, "unmanage.vm.preserve.nics", "false",
"If set to true, do not remove VM nics (and its MAC addresses) when unmanaging a VM, leaving them allocated but not reserved. " +
"If set to false, nics are removed and MAC addresses can be reassigned", true, ConfigKey.Scope.Zone);
}

View File

@ -23,9 +23,7 @@ import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import com.cloud.utils.component.PluggableService;
public interface VmImportService extends PluggableService {
public interface VmImportService {
ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd);
UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd);
}

View File

@ -0,0 +1,27 @@
// 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;
public class PrepareUnmanageVMInstanceAnswer extends Answer {
public PrepareUnmanageVMInstanceAnswer() {
}
public PrepareUnmanageVMInstanceAnswer(PrepareUnmanageVMInstanceCommand cmd, boolean result, String details) {
super(cmd, result, details);
}
}

View File

@ -0,0 +1,39 @@
// 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;
public class PrepareUnmanageVMInstanceCommand extends Command {
private String instanceName;
public PrepareUnmanageVMInstanceCommand() {
}
public String getInstanceName() {
return instanceName;
}
public void setInstanceName(String instanceName) {
this.instanceName = instanceName;
}
@Override
public boolean executeInSequence() {
return false;
}
}

View File

@ -58,4 +58,6 @@ public interface VirtualMachineGuru {
* @return
*/
void prepareStop(VirtualMachineProfile profile);
void finalizeUnmanage(VirtualMachine vm);
}

View File

@ -213,4 +213,11 @@ public interface VirtualMachineManager extends Manager {
void migrateForScale(String vmUuid, long srcHostId, DeployDestination dest, Long newSvcOfferingId) throws ResourceUnavailableException, ConcurrentOperationException;
boolean getExecuteInSequence(HypervisorType hypervisorType);
/**
* Unmanage a VM from CloudStack:
* - Remove the references of the VM and its volumes, nics, IPs from database
* - Keep the VM as it is on the hypervisor
*/
boolean unmanage(String vmUuid);
}

View File

@ -317,5 +317,7 @@ public interface NetworkOrchestrationService {
*/
void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile);
Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException;
Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException;
void unmanageNics(VirtualMachineProfile vm);
}

View File

@ -149,4 +149,9 @@ public interface VolumeOrchestrationService {
*/
DiskProfile importVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template,
Account owner, Long deviceId, Long poolId, String path, String chainInfo);
/**
* Unmanage VM volumes
*/
void unmanageVolumes(long vmId);
}

View File

@ -35,5 +35,5 @@ public interface VMSnapshotStrategy {
* @param vmSnapshot vm snapshot to be marked as deleted.
* @return true if vm snapshot removed from DB, false if not.
*/
boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot);
boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot, boolean unmanage);
}

View File

@ -93,4 +93,6 @@ public interface VolumeService {
SnapshotInfo takeSnapshot(VolumeInfo volume);
VolumeInfo updateHypervisorSnapshotReserveForVolume(DiskOffering diskOffering, long volumeId, HypervisorType hyperType);
void unmanageVolume(long volumeId);
}

View File

@ -74,8 +74,10 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.cloudstack.vm.UnmanagedVMsManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
@ -212,6 +214,7 @@ import com.cloud.utils.db.DB;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionCallbackWithException;
import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
import com.cloud.utils.db.TransactionLegacy;
@ -1488,6 +1491,88 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
}
@Override
public boolean unmanage(String vmUuid) {
VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
if (vm == null || vm.getRemoved() != null) {
throw new CloudRuntimeException("Could not find VM with id = " + vmUuid);
}
final List<VmWorkJobVO> pendingWorkJobs = _workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, vm.getId());
if (CollectionUtils.isNotEmpty(pendingWorkJobs) || _haMgr.hasPendingHaWork(vm.getId())) {
String msg = "There are pending jobs or HA tasks working on the VM with id: " + vm.getId() + ", can't unmanage the VM.";
s_logger.info(msg);
throw new ConcurrentOperationException(msg);
}
Boolean result = Transaction.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Unmanaging vm " + vm);
}
final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm);
final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType());
final VirtualMachineGuru guru = getVmGuru(vm);
try {
unmanageVMSnapshots(vm);
unmanageVMNics(profile, vm);
unmanageVMVolumes(vm);
guru.finalizeUnmanage(vm);
} catch (Exception e) {
s_logger.error("Error while unmanaging VM " + vm, e);
return false;
}
return true;
}
});
return BooleanUtils.isTrue(result);
}
/**
* Clean up VM snapshots (if any) from DB
*/
private void unmanageVMSnapshots(VMInstanceVO vm) {
_vmSnapshotMgr.deleteVMSnapshotsFromDB(vm.getId(), true);
}
/**
* Clean up volumes for a VM to be unmanaged from CloudStack
*/
private void unmanageVMVolumes(VMInstanceVO vm) {
final Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId();
if (hostId != null) {
volumeMgr.revokeAccess(vm.getId(), hostId);
}
volumeMgr.unmanageVolumes(vm.getId());
List<Map<String, String>> targets = getTargets(hostId, vm.getId());
if (hostId != null && CollectionUtils.isNotEmpty(targets)) {
removeDynamicTargets(hostId, targets);
}
}
/**
* Clean up NICs for a VM to be unmanaged from CloudStack:
* - If 'unmanage.vm.preserve.nics' = true: then the NICs are not removed but still Allocated, to preserve MAC addresses
* - If 'unmanage.vm.preserve.nics' = false: then the NICs are removed while unmanaging
*/
private void unmanageVMNics(VirtualMachineProfile profile, VMInstanceVO vm) {
s_logger.debug("Cleaning up NICs");
Boolean preserveNics = UnmanagedVMsManager.UnmanageVMPreserveNic.valueIn(vm.getDataCenterId());
if (BooleanUtils.isTrue(preserveNics)) {
s_logger.debug("Preserve NICs configuration enabled");
profile.setParameter(VirtualMachineProfile.Param.PreserveNics, true);
}
_networkMgr.unmanageNics(profile);
}
private List<Map<String, String>> getVolumesToDisconnect(VirtualMachine vm) {
List<Map<String, String>> volumesToDisconnect = new ArrayList<>();
@ -1978,7 +2063,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
else {
if (expunge) {
_vmSnapshotMgr.deleteVMSnapshotsFromDB(vm.getId());
_vmSnapshotMgr.deleteVMSnapshotsFromDB(vm.getId(), false);
}
}
}

View File

@ -22,6 +22,7 @@ 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;
@ -38,6 +39,8 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.network.dao.NetworkDetailVO;
import com.cloud.network.dao.NetworkDetailsDao;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
@ -53,6 +56,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.commons.lang.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
@ -2057,8 +2061,12 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
}
}
nic.setState(Nic.State.Deallocating);
_nicDao.update(nic.getId(), nic);
Boolean preserveNics = (Boolean) vm.getParameter(VirtualMachineProfile.Param.PreserveNics);
if (BooleanUtils.isNotTrue(preserveNics)) {
nic.setState(Nic.State.Deallocating);
_nicDao.update(nic.getId(), nic);
}
final NicProfile profile = new NicProfile(nic, network, null, null, null, _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(
vm.getHypervisorType(), network));
@ -2113,7 +2121,9 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName());
guru.deallocate(network, profile, vm);
_nicDao.remove(nic.getId());
if (BooleanUtils.isNotTrue(preserveNics)) {
_nicDao.remove(nic.getId());
}
s_logger.debug("Removed nic id=" + nic.getId());
//remove the secondary ip addresses corresponding to to this nic
@ -4002,7 +4012,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
@DB
@Override
public Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses)
public Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final boolean forced)
throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
s_logger.debug("Allocating nic for vm " + vm.getUuid() + " in network " + network + " during import");
String guestIp = null;
@ -4024,6 +4034,17 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
final NicVO vo = Transaction.execute(new TransactionCallback<NicVO>() {
@Override
public NicVO doInTransaction(TransactionStatus status) {
NicVO existingNic = _nicDao.findByNetworkIdAndMacAddress(network.getId(), 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);
}
NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType());
vo.setMacAddress(macAddress);
vo.setAddressFormat(Networks.AddressFormat.Ip4);
@ -4065,6 +4086,24 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
return new Pair<NicProfile, Integer>(vmNic, Integer.valueOf(deviceId));
}
@Override
public void unmanageNics(VirtualMachineProfile vm) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Unmanaging NICs for VM: " + vm.getId());
}
VirtualMachine virtualMachine = vm.getVirtualMachine();
final List<NicVO> nics = _nicDao.listByVmId(vm.getId());
for (final NicVO nic : nics) {
removeNic(vm, nic);
NetworkVO network = _networksDao.findById(nic.getNetworkId());
if (virtualMachine.getState() != VirtualMachine.State.Stopped) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, virtualMachine.getAccountId(), virtualMachine.getDataCenterId(), virtualMachine.getId(),
Long.toString(nic.getId()), network.getNetworkOfferingId(), null, 0L, virtualMachine.getClass().getName(), virtualMachine.getUuid(), virtualMachine.isDisplay());
}
}
}
@Override
public String getConfigComponentName() {
return NetworkOrchestrationService.class.getSimpleName();

View File

@ -1680,4 +1680,27 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
vol = _volsDao.persist(vol);
return toDiskProfile(vol, offering);
}
@Override
public void unmanageVolumes(long vmId) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Unmanaging storage for vm: " + vmId);
}
final List<VolumeVO> volumesForVm = _volsDao.findByInstance(vmId);
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
for (VolumeVO vol : volumesForVm) {
boolean volumeAlreadyDestroyed = (vol.getState() == Volume.State.Destroy || vol.getState() == Volume.State.Expunged
|| vol.getState() == Volume.State.Expunging);
if (volumeAlreadyDestroyed) {
s_logger.debug("Skipping destroy for the volume " + vol + " as its in state " + vol.getState().toString());
} else {
volService.unmanageVolume(vol.getId());
}
}
}
});
}
}

View File

@ -463,4 +463,24 @@ public class NetworkOrchestratorTest extends TestCase {
testOrchastrator.validateLockedRequestedIp(ipVoSpy, lockedIp);
}
@Test
public void testDontReleaseNicWhenPreserveNicsSettingEnabled() {
VirtualMachineProfile vm = mock(VirtualMachineProfile.class);
NicVO nic = mock(NicVO.class);
NetworkVO network = mock(NetworkVO.class);
when(vm.getType()).thenReturn(Type.User);
when(network.getGuruName()).thenReturn(guruName);
when(testOrchastrator._networksDao.findById(nic.getNetworkId())).thenReturn(network);
Long nicId = 1L;
when(nic.getId()).thenReturn(nicId);
when(vm.getParameter(VirtualMachineProfile.Param.PreserveNics)).thenReturn(true);
testOrchastrator.removeNic(vm, nic);
verify(nic, never()).setState(Nic.State.Deallocating);
verify(testOrchastrator._nicDao, never()).remove(nicId);
}
}

View File

@ -426,7 +426,7 @@ public class DefaultVMSnapshotStrategy extends ManagerBase implements VMSnapshot
}
@Override
public boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot) {
public boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot, boolean unmanage) {
try {
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.ExpungeRequested);
} catch (NoTransitionException e) {
@ -435,9 +435,14 @@ public class DefaultVMSnapshotStrategy extends ManagerBase implements VMSnapshot
}
UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
long full_chain_size = 0;
for (VolumeObjectTO volumeTo: volumeTOs) {
volumeTo.setSize(0);
publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo);
full_chain_size += volumeTo.getSize();
}
if (unmanage) {
publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L);
}
return vmSnapshotDao.remove(vmSnapshot.getId());
}

View File

@ -2108,4 +2108,21 @@ public class VolumeServiceImpl implements VolumeService {
return volFactory.getVolume(volumeId);
}
@DB
@Override
/**
* The volume must be marked as expunged on DB to exclude it from the storage cleanup task
*/
public void unmanageVolume(long volumeId) {
VolumeInfo vol = volFactory.getVolume(volumeId);
if (vol != null) {
vol.stateTransit(Volume.Event.DestroyRequested);
snapshotMgr.deletePoliciesForVolume(volumeId);
vol.stateTransit(Volume.Event.OperationSucceeded);
vol.stateTransit(Volume.Event.ExpungingRequested);
vol.stateTransit(Volume.Event.OperationSucceeded);
volDao.remove(vol.getId());
}
}
}

View File

@ -129,6 +129,8 @@ import com.cloud.agent.api.PlugNicAnswer;
import com.cloud.agent.api.PlugNicCommand;
import com.cloud.agent.api.PrepareForMigrationAnswer;
import com.cloud.agent.api.PrepareForMigrationCommand;
import com.cloud.agent.api.PrepareUnmanageVMInstanceAnswer;
import com.cloud.agent.api.PrepareUnmanageVMInstanceCommand;
import com.cloud.agent.api.PvlanSetupCommand;
import com.cloud.agent.api.ReadyAnswer;
import com.cloud.agent.api.ReadyCommand;
@ -561,6 +563,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
answer = execute((UnregisterNicCommand) cmd);
} else if (clz == GetUnmanagedInstancesCommand.class) {
answer = execute((GetUnmanagedInstancesCommand) cmd);
} else if (clz == PrepareUnmanageVMInstanceCommand.class) {
answer = execute((PrepareUnmanageVMInstanceCommand) cmd);
} else {
answer = Answer.createUnsupportedCommandAnswer(cmd);
}
@ -7148,4 +7152,26 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
}
return new GetUnmanagedInstancesAnswer(cmd, "", unmanagedInstances);
}
private Answer execute(PrepareUnmanageVMInstanceCommand cmd) {
s_logger.debug("Verify VMware instance: " + cmd.getInstanceName() + " is available before unmanaging VM");
VmwareContext context = getServiceContext();
VmwareHypervisorHost hyperHost = getHyperHost(context);
String instanceName = cmd.getInstanceName();
try {
ManagedObjectReference dcMor = hyperHost.getHyperHostDatacenter();
DatacenterMO dataCenterMo = new DatacenterMO(getServiceContext(), dcMor);
VirtualMachineMO vm = dataCenterMo.findVm(instanceName);
if (vm == null) {
return new PrepareUnmanageVMInstanceAnswer(cmd, false, "Cannot find VM with name " + instanceName +
" in datacenter " + dataCenterMo.getName());
}
} catch (Exception e) {
s_logger.error("Error trying to verify if VM to unmanage exists", e);
return new PrepareUnmanageVMInstanceAnswer(cmd, false, "Error: " + e.getMessage());
}
return new PrepareUnmanageVMInstanceAnswer(cmd, true, "OK");
}
}

View File

@ -596,4 +596,8 @@ public class ElasticLoadBalancerManagerImpl extends ManagerBase implements Elast
public void prepareStop(VirtualMachineProfile profile) {
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
}

View File

@ -356,6 +356,10 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In
public void prepareStop(final VirtualMachineProfile profile) {
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
@Override
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
final Map<String, String> configs = _configDao.getConfiguration("AgentManager", params);

View File

@ -189,6 +189,10 @@ public class NetScalerVMManagerImpl extends ManagerBase implements NetScalerVMMa
public void prepareStop(VirtualMachineProfile profile) {
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_itMgr.registerGuru(VirtualMachine.Type.NetScalerVm, this);

View File

@ -1735,6 +1735,10 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
public void prepareStop(VirtualMachineProfile profile) {
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
public List<ConsoleProxyAllocator> getConsoleProxyAllocators() {
return _consoleProxyAllocators;
}

View File

@ -3148,6 +3148,10 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
}
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
@Override
public VirtualRouter findRouter(final long routerId) {
return _routerDao.findById(routerId);

View File

@ -47,9 +47,12 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.cloud.exception.UnsupportedServiceException;
import com.cloud.hypervisor.Hypervisor;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.affinity.AffinityGroupVMMapVO;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
@ -5038,7 +5041,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new PermissionDeniedException("Expunging a vm can only be done by an Admin. Or when the allow.user.expunge.recover.vm key is set.");
}
_vmSnapshotMgr.deleteVMSnapshotsFromDB(vmId);
_vmSnapshotMgr.deleteVMSnapshotsFromDB(vmId, false);
boolean status;
@ -6951,6 +6954,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
private void encryptAndStorePassword(UserVmVO vm, String password) {
String sshPublicKey = vm.getDetail(VmDetailConstants.SSH_PUBLIC_KEY);
if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password")) {
@ -7112,4 +7119,139 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
id, instanceName, uuidName, hypervisorType, customParameters,
null, null, null, powerState);
}
@Override
public boolean unmanageUserVM(Long vmId) {
UserVmVO vm = _vmDao.findById(vmId);
if (vm == null || vm.getRemoved() != null) {
throw new InvalidParameterValueException("Unable to find a VM with ID = " + vmId);
}
vm = _vmDao.acquireInLockTable(vm.getId());
boolean result;
try {
if (vm.getState() != State.Running && vm.getState() != State.Stopped) {
s_logger.debug("VM ID = " + vmId + " is not running or stopped, cannot be unmanaged");
return false;
}
if (vm.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
throw new UnsupportedServiceException("Unmanaging a VM is currently allowed for VMware VMs only");
}
List<VolumeVO> volumes = _volsDao.findByInstance(vm.getId());
checkUnmanagingVMOngoingVolumeSnapshots(vm);
checkUnmanagingVMVolumes(vm, volumes);
result = _itMgr.unmanage(vm.getUuid());
if (result) {
cleanupUnmanageVMResources(vm.getId());
unmanageVMFromDB(vm.getId());
publishUnmanageVMUsageEvents(vm, volumes);
} else {
throw new CloudRuntimeException("Error while unmanaging VM: " + vm.getUuid());
}
} catch (Exception e) {
s_logger.error("Could not unmanage VM " + vm.getUuid(), e);
throw new CloudRuntimeException(e);
} finally {
_vmDao.releaseFromLockTable(vm.getId());
}
return true;
}
/*
Generate usage events related to unmanaging a VM
*/
private void publishUnmanageVMUsageEvents(UserVmVO vm, List<VolumeVO> volumes) {
postProcessingUnmanageVMVolumes(volumes, vm);
postProcessingUnmanageVM(vm);
}
/*
Cleanup the VM from resources and groups
*/
private void cleanupUnmanageVMResources(long vmId) {
cleanupVmResources(vmId);
removeVMFromAffinityGroups(vmId);
}
private void unmanageVMFromDB(long vmId) {
VMInstanceVO vm = _vmInstanceDao.findById(vmId);
userVmDetailsDao.removeDetails(vmId);
vm.setState(State.Expunging);
vm.setRemoved(new Date());
_vmInstanceDao.update(vm.getId(), vm);
}
/*
Remove VM from affinity groups after unmanaging
*/
private void removeVMFromAffinityGroups(long vmId) {
List<AffinityGroupVMMapVO> affinityGroups = _affinityGroupVMMapDao.listByInstanceId(vmId);
if (affinityGroups.size() > 0) {
s_logger.debug("Cleaning up VM from affinity groups after unmanaging");
for (AffinityGroupVMMapVO map : affinityGroups) {
_affinityGroupVMMapDao.expunge(map.getId());
}
}
}
/*
Decrement VM resources and generate usage events after unmanaging VM
*/
private void postProcessingUnmanageVM(UserVmVO vm) {
ServiceOfferingVO offering = _serviceOfferingDao.findById(vm.getServiceOfferingId());
// First generate a VM stop event if the VM was not stopped already
if (vm.getState() != State.Stopped) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_STOP, vm.getAccountId(), vm.getDataCenterId(),
vm.getId(), vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(),
vm.getHypervisorType().toString(), VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplayVm());
resourceCountDecrement(vm.getAccountId(), vm.isDisplayVm(), new Long(offering.getCpu()), new Long(offering.getRamSize()));
}
// VM destroy usage event
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_DESTROY, vm.getAccountId(), vm.getDataCenterId(),
vm.getId(), vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(),
vm.getHypervisorType().toString(), VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplayVm());
resourceCountDecrement(vm.getAccountId(), vm.isDisplayVm(), new Long(offering.getCpu()), new Long(offering.getRamSize()));
}
/*
Decrement resources for volumes and generate usage event for ROOT volume after unmanaging VM.
Usage events for DATA disks are published by the transition listener: @see VolumeStateListener#postStateTransitionEvent
*/
private void postProcessingUnmanageVMVolumes(List<VolumeVO> volumes, UserVmVO vm) {
for (VolumeVO volume : volumes) {
if (volume.getVolumeType() == Volume.Type.ROOT) {
//
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
Volume.class.getName(), volume.getUuid(), volume.isDisplayVolume());
}
_resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.volume);
_resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, new Long(volume.getSize()));
}
}
private void checkUnmanagingVMOngoingVolumeSnapshots(UserVmVO vm) {
s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vm.getId());
if (checkStatusOfVolumeSnapshots(vm.getId(), Volume.Type.ROOT)) {
throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on ROOT volume, vm unmanage is not permitted, please try again later.");
}
s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vm.getId());
}
private void checkUnmanagingVMVolumes(UserVmVO vm, List<VolumeVO> volumes) {
for (VolumeVO volume : volumes) {
if (volume.getInstanceId() == null || !volume.getInstanceId().equals(vm.getId())) {
throw new CloudRuntimeException("Invalid state for volume with ID " + volume.getId() + " of VM " +
vm.getId() +": it is not attached to VM");
} else if (volume.getVolumeType() != Volume.Type.ROOT && volume.getVolumeType() != Volume.Type.DATADISK) {
throw new CloudRuntimeException("Invalid type for volume with ID " + volume.getId() +
": ROOT or DATADISK expected but got " + volume.getVolumeType());
}
}
}
}

View File

@ -1282,7 +1282,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
}
@Override
public boolean deleteVMSnapshotsFromDB(Long vmId) {
public boolean deleteVMSnapshotsFromDB(Long vmId, boolean unmanage) {
List<VMSnapshotVO> listVmSnapshots = _vmSnapshotDao.findByVm(vmId);
if (listVmSnapshots == null || listVmSnapshots.isEmpty()) {
return true;
@ -1290,7 +1290,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
for (VMSnapshotVO snapshot : listVmSnapshots) {
try {
VMSnapshotStrategy strategy = findVMSnapshotStrategy(snapshot);
if (! strategy.deleteVMSnapshotFromDB(snapshot)) {
if (! strategy.deleteVMSnapshotFromDB(snapshot, unmanage)) {
s_logger.error("Couldn't delete vm snapshot with id " + snapshot.getId());
return false;
}

View File

@ -25,6 +25,17 @@ import java.util.Set;
import javax.inject.Inject;
import com.cloud.agent.api.PrepareUnmanageVMInstanceAnswer;
import com.cloud.agent.api.PrepareUnmanageVMInstanceCommand;
import com.cloud.event.ActionEvent;
import com.cloud.exception.UnsupportedServiceException;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseGenerator;
@ -32,6 +43,7 @@ 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.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;
@ -40,6 +52,7 @@ 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.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@ -127,9 +140,9 @@ import com.cloud.vm.dao.VMInstanceDao;
import com.google.common.base.Strings;
import com.google.gson.Gson;
public class VmImportManagerImpl implements VmImportService {
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(VmImportManagerImpl.class);
private static final Logger LOGGER = Logger.getLogger(UnmanagedVMsManagerImpl.class);
@Inject
private AgentManager agentManager;
@ -191,10 +204,16 @@ public class VmImportManagerImpl implements VmImportService {
private GuestOSDao guestOSDao;
@Inject
private GuestOSHypervisorDao guestOSHypervisorDao;
@Inject
private VMSnapshotDao vmSnapshotDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private UserVmDao userVmDao;
protected Gson gson;
public VmImportManagerImpl() {
public UnmanagedVMsManagerImpl() {
gson = GsonHelper.getGsonLogger();
}
@ -680,8 +699,8 @@ public class VmImportManagerImpl implements VmImportService {
return new Pair<DiskProfile, StoragePool>(profile, storagePool);
}
private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, boolean isDefaultNic) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), 0, network, isDefaultNic, vm, ipAddresses);
private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, boolean isDefaultNic, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), 0, network, isDefaultNic, vm, ipAddresses, forced);
if (result == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC ID: %s import failed", nic.getNicId()));
}
@ -850,12 +869,16 @@ public class VmImportManagerImpl implements VmImportService {
}
try {
if (!serviceOfferingVO.isDynamic()) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_IMPORT, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplayVm());
} else {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_IMPORT, userVm.getAccountId(), userVm.getAccountId(), userVm.getDataCenterId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, userVm.getAccountId(), userVm.getAccountId(), userVm.getDataCenterId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.getDetails(), userVm.isDisplayVm());
}
if (userVm.getState() == VirtualMachine.State.Running) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_START, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplayVm());
}
} catch (Exception e) {
LOGGER.error(String.format("Failed to publish usage records during VM import for unmanaged vm %s", userVm.getInstanceName()), e);
cleanupFailedImportVM(userVm);
@ -876,13 +899,24 @@ public class VmImportManagerImpl implements VmImportService {
resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.volume, volume.isDisplayVolume());
resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize());
}
List<NicVO> nics = nicDao.listByVmId(userVm.getId());
for (NicVO nic : nics) {
try {
NetworkVO network = networkDao.findById(nic.getNetworkId());
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(),
Long.toString(nic.getId()), network.getNetworkOfferingId(), null, 1L, VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplay());
} catch (Exception e) {
LOGGER.error(String.format("Failed to publish network usage records during VM import. %s", Strings.nullToEmpty(e.getMessage())));
}
}
}
private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedInstance, final String instanceName, final DataCenter zone, final Cluster cluster, final HostVO host,
final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap,
final Map<String, Long> nicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap,
final Map<String, String> details, final boolean migrateAllowed) {
final Map<String, String> details, final boolean migrateAllowed, final boolean forced) {
UserVm userVm = null;
ServiceOfferingVO validatedServiceOffering = null;
@ -986,7 +1020,7 @@ public class VmImportManagerImpl implements VmImportService {
for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) {
Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId()));
Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId());
importNic(nic, userVm, network, ipAddresses, firstNic);
importNic(nic, userVm, network, ipAddresses, firstNic, forced);
firstNic = false;
}
} catch (Exception e) {
@ -1139,6 +1173,7 @@ public class VmImportManagerImpl implements VmImportService {
final Map<String, Network.IpAddresses> nicIpAddressMap = cmd.getNicIpAddressList();
final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
final Map<String, String> details = cmd.getDetails();
final boolean forced = cmd.isForced();
List<HostVO> hosts = resourceManager.listHostsInClusterByStatus(clusterId, Status.Up);
UserVm userVm = null;
List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
@ -1192,7 +1227,7 @@ public class VmImportManagerImpl implements VmImportService {
template, displayName, hostName, caller, owner, userId,
serviceOffering, dataDiskOfferingMap,
nicNetworkMap, nicIpAddressMap,
details, cmd.getMigrateAllowed());
details, cmd.getMigrateAllowed(), forced);
break;
}
}
@ -1211,6 +1246,124 @@ public class VmImportManagerImpl implements VmImportService {
final List<Class<?>> cmdList = new ArrayList<Class<?>>();
cmdList.add(ListUnmanagedInstancesCmd.class);
cmdList.add(ImportUnmanagedInstanceCmd.class);
cmdList.add(UnmanageVMInstanceCmd.class);
return cmdList;
}
/**
* Perform validations before attempting to unmanage a VM from CloudStack:
* - VM must not have any associated volume snapshot
* - VM must not have an attached ISO
*/
private void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) {
if (hasVolumeSnapshotsPriorToUnmanageVM(vmVO)) {
throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() +
" as there are volume snapshots for its volume(s). Please remove snapshots before unmanaging.");
}
if (hasISOAttached(vmVO)) {
throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() +
" as there is an ISO attached. Please detach ISO before unmanaging.");
}
}
private boolean hasVolumeSnapshotsPriorToUnmanageVM(VMInstanceVO vmVO) {
List<VolumeVO> volumes = volumeDao.findByInstance(vmVO.getId());
for (VolumeVO volume : volumes) {
List<SnapshotVO> snaps = snapshotDao.listByVolumeId(volume.getId());
if (CollectionUtils.isNotEmpty(snaps)) {
for (SnapshotVO snap : snaps) {
if (snap.getState() != Snapshot.State.Destroyed && snap.getRemoved() == null) {
return true;
}
}
}
}
return false;
}
private boolean hasISOAttached(VMInstanceVO vmVO) {
UserVmVO userVM = userVmDao.findById(vmVO.getId());
if (userVM == null) {
throw new InvalidParameterValueException("Could not find user VM with ID = " + vmVO.getUuid());
}
return userVM.getIsoId() != null;
}
/**
* Find a suitable host within the scope of the VM to unmanage to verify the VM exists
*/
private Long findSuitableHostId(VMInstanceVO vmVO) {
Long hostId = vmVO.getHostId();
if (hostId == null) {
long zoneId = vmVO.getDataCenterId();
List<HostVO> hosts = hostDao.listAllHostsUpByZoneAndHypervisor(zoneId, vmVO.getHypervisorType());
for (HostVO host : hosts) {
if (host.isInMaintenanceStates() || host.getState() != Status.Up || host.getStatus() != Status.Up) {
continue;
}
hostId = host.getId();
break;
}
}
if (hostId == null) {
throw new CloudRuntimeException("Cannot find a host to verify if the VM to unmanage " +
"with id = " + vmVO.getUuid() + " exists.");
}
return hostId;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_UNMANAGE, eventDescription = "unmanaging VM", async = true)
public boolean unmanageVMInstance(long vmId) {
VMInstanceVO vmVO = vmDao.findById(vmId);
if (vmVO == null || vmVO.getRemoved() != null) {
throw new InvalidParameterValueException("Could not find VM to unmanage, it is either removed or not existing VM");
} else if (vmVO.getState() != VirtualMachine.State.Running && vmVO.getState() != VirtualMachine.State.Stopped) {
throw new InvalidParameterValueException("VM with id = " + vmVO.getUuid() + " must be running or stopped to be unmanaged");
} else if (vmVO.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
throw new UnsupportedServiceException("Unmanage VM is currently allowed for VMware VMs only");
} else if (vmVO.getType() != VirtualMachine.Type.User) {
throw new UnsupportedServiceException("Unmanage VM is currently allowed for guest VMs only");
}
performUnmanageVMInstancePrechecks(vmVO);
Long hostId = findSuitableHostId(vmVO);
String instanceName = vmVO.getInstanceName();
if (!existsVMToUnmanage(instanceName, hostId)) {
throw new CloudRuntimeException("VM with id = " + vmVO.getUuid() + " is not found in the hypervisor");
}
return userVmManager.unmanageUserVM(vmId);
}
/**
* Verify the VM to unmanage exists on the hypervisor
*/
private boolean existsVMToUnmanage(String instanceName, Long hostId) {
PrepareUnmanageVMInstanceCommand command = new PrepareUnmanageVMInstanceCommand();
command.setInstanceName(instanceName);
Answer ans = agentManager.easySend(hostId, command);
if (!(ans instanceof PrepareUnmanageVMInstanceAnswer)) {
throw new CloudRuntimeException("Error communicating with host " + hostId);
}
PrepareUnmanageVMInstanceAnswer answer = (PrepareUnmanageVMInstanceAnswer) ans;
if (!answer.getResult()) {
LOGGER.error("Error verifying VM " + instanceName + " exists on host with ID = " + hostId + ": " + answer.getDetails());
}
return answer.getResult();
}
@Override
public String getConfigComponentName() {
return UnmanagedVMsManagerImpl.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] { UnmanageVMPreserveNic };
}
}

View File

@ -35,6 +35,6 @@
<property name="name" value="LXCGuru" />
</bean>
<bean id="vmImportService" class="org.apache.cloudstack.vm.VmImportManagerImpl" />
<bean id="vmImportService" class="org.apache.cloudstack.vm.UnmanagedVMsManagerImpl" />
</beans>

View File

@ -985,7 +985,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
}
@Override
public Pair<NicProfile, Integer> importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses) {
public Pair<NicProfile, Integer> importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, boolean forced) {
return null;
}
@Override
public void unmanageNics(VirtualMachineProfile vm) {
}
}

View File

@ -19,17 +19,29 @@ package org.apache.cloudstack.vm;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.cloud.exception.UnsupportedServiceException;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.vm.NicVO;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
@ -111,10 +123,10 @@ import com.cloud.vm.dao.VMInstanceDao;
@RunWith(PowerMockRunner.class)
@PrepareForTest(UsageEventUtils.class)
public class VmImportManagerImplTest {
public class UnmanagedVMsManagerImplTest {
@InjectMocks
private VmImportService vmIngestionService = new VmImportManagerImpl();
private UnmanagedVMsManager unmanagedVMsManager = new UnmanagedVMsManagerImpl();
@Mock
private UserVmManager userVmManager;
@ -160,6 +172,21 @@ public class VmImportManagerImplTest {
private NetworkModel networkModel;
@Mock
private ConfigurationDao configurationDao;
@Mock
private VMSnapshotDao vmSnapshotDao;
@Mock
private SnapshotDao snapshotDao;
@Mock
private UserVmDao userVmDao;
@Mock
private NicDao nicDao;
@Mock
private VMInstanceVO virtualMachine;
@Mock
private NicVO nicVO;
private static final long virtualMachineId = 1L;
@Before
public void setUp() throws Exception {
@ -281,7 +308,7 @@ public class VmImportManagerImplTest {
NicProfile profile = Mockito.mock(NicProfile.class);
Integer deviceId = 100;
Pair<NicProfile, Integer> pair = new Pair<NicProfile, Integer>(profile, deviceId);
when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class))).thenReturn(pair);
when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), anyBoolean())).thenReturn(pair);
when(volumeManager.importVolume(Mockito.any(Volume.Type.class), Mockito.anyString(), Mockito.any(DiskOffering.class), Mockito.anyLong(),
Mockito.anyLong(), Mockito.anyLong(), Mockito.any(VirtualMachine.class), Mockito.any(VirtualMachineTemplate.class),
Mockito.any(Account.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString())).thenReturn(Mockito.mock(DiskProfile.class));
@ -291,6 +318,18 @@ public class VmImportManagerImplTest {
userVmResponse.setInstanceName(instance.getName());
userVmResponses.add(userVmResponse);
when(responseGenerator.createUserVmResponse(Mockito.any(ResponseObject.ResponseView.class), Mockito.anyString(), Mockito.any(UserVm.class))).thenReturn(userVmResponses);
when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine);
when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Running);
when(virtualMachine.getInstanceName()).thenReturn("i-2-7-VM");
when(virtualMachine.getId()).thenReturn(virtualMachineId);
VolumeVO volumeVO = mock(VolumeVO.class);
when(volumeDao.findByInstance(virtualMachineId)).thenReturn(Collections.singletonList(volumeVO));
when(volumeVO.getInstanceId()).thenReturn(virtualMachineId);
when(volumeVO.getId()).thenReturn(virtualMachineId);
when(nicDao.listByVmId(virtualMachineId)).thenReturn(Collections.singletonList(nicVO));
when(nicVO.getNetworkId()).thenReturn(1L);
when(networkDao.findById(1L)).thenReturn(networkVO);
}
@After
@ -301,7 +340,7 @@ public class VmImportManagerImplTest {
@Test
public void listUnmanagedInstancesTest() {
ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class);
vmIngestionService.listUnmanagedInstances(cmd);
unmanagedVMsManager.listUnmanagedInstances(cmd);
}
@Test(expected = InvalidParameterValueException.class)
@ -310,7 +349,7 @@ public class VmImportManagerImplTest {
ClusterVO cluster = new ClusterVO(1, 1, "Cluster");
cluster.setHypervisorType(Hypervisor.HypervisorType.KVM.toString());
when(clusterDao.findById(Mockito.anyLong())).thenReturn(cluster);
vmIngestionService.listUnmanagedInstances(cmd);
unmanagedVMsManager.listUnmanagedInstances(cmd);
}
@Test(expected = PermissionDeniedException.class)
@ -320,7 +359,7 @@ public class VmImportManagerImplTest {
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class);
vmIngestionService.listUnmanagedInstances(cmd);
unmanagedVMsManager.listUnmanagedInstances(cmd);
}
@Test
@ -330,7 +369,7 @@ public class VmImportManagerImplTest {
when(importUnmanageInstanceCmd.getAccountName()).thenReturn(null);
when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null);
PowerMockito.mockStatic(UsageEventUtils.class);
vmIngestionService.importUnmanagedInstance(importUnmanageInstanceCmd);
unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
}
@Test(expected = InvalidParameterValueException.class)
@ -339,7 +378,7 @@ public class VmImportManagerImplTest {
when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance");
when(importUnmanageInstanceCmd.getName()).thenReturn("some name");
when(importUnmanageInstanceCmd.getMigrateAllowed()).thenReturn(false);
vmIngestionService.importUnmanagedInstance(importUnmanageInstanceCmd);
unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
}
@Test(expected = ServerApiException.class)
@ -348,6 +387,44 @@ public class VmImportManagerImplTest {
when(importUnmanageInstanceCmd.getName()).thenReturn("SomeInstance");
when(importUnmanageInstanceCmd.getAccountName()).thenReturn(null);
when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null);
vmIngestionService.importUnmanagedInstance(importUnmanageInstanceCmd);
unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
}
@Test(expected = InvalidParameterValueException.class)
public void unmanageVMInstanceMissingInstanceTest() {
long notExistingId = 10L;
unmanagedVMsManager.unmanageVMInstance(notExistingId);
}
@Test(expected = InvalidParameterValueException.class)
public void unmanageVMInstanceDestroyedInstanceTest() {
when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Destroyed);
unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
}
@Test(expected = InvalidParameterValueException.class)
public void unmanageVMInstanceExpungedInstanceTest() {
when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Expunging);
unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
}
@Test(expected = UnsupportedServiceException.class)
public void unmanageVMInstanceExistingVMSnapshotsTest() {
when(vmSnapshotDao.findByVm(virtualMachineId)).thenReturn(Arrays.asList(new VMSnapshotVO(), new VMSnapshotVO()));
unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
}
@Test(expected = UnsupportedServiceException.class)
public void unmanageVMInstanceExistingVolumeSnapshotsTest() {
when(snapshotDao.listByVolumeId(virtualMachineId)).thenReturn(Arrays.asList(new SnapshotVO(), new SnapshotVO()));
unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
}
@Test(expected = UnsupportedServiceException.class)
public void unmanageVMInstanceExistingISOAttachedTest() {
UserVmVO userVmVO = mock(UserVmVO.class);
when(userVmDao.findById(virtualMachineId)).thenReturn(userVmVO);
when(userVmVO.getIsoId()).thenReturn(3L);
unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
}
}

View File

@ -1496,6 +1496,10 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar
}
@Override
public void finalizeUnmanage(VirtualMachine vm) {
}
public List<SecondaryStorageVmAllocator> getSecondaryStorageVmAllocators() {
return _ssVmAllocators;
}

View File

@ -25,7 +25,9 @@ from marvin.cloudstackAPI import (recoverVirtualMachine,
provisionCertificate,
updateConfiguration,
migrateVirtualMachine,
migrateVirtualMachineWithVolume)
migrateVirtualMachineWithVolume,
unmanageVirtualMachine,
listUnmanagedInstances)
from marvin.lib.utils import *
from marvin.lib.base import (Account,
@ -37,13 +39,17 @@ from marvin.lib.base import (Account,
Configurations,
StoragePool,
Volume,
DiskOffering)
DiskOffering,
NetworkOffering,
Network)
from marvin.lib.common import (get_domain,
get_zone,
get_template,
list_hosts)
list_hosts,
list_virtual_machines)
from marvin.codes import FAILED, PASS
from nose.plugins.attrib import attr
from marvin.lib.decoratorGenerators import skipTestIf
# Import System modules
import time
@ -1512,3 +1518,152 @@ class TestKVMLiveMigration(cloudstackTestCase):
target_host.id,
"HostID not as expected")
class TestUnmanageVM(cloudstackTestCase):
@classmethod
def setUpClass(cls):
testClient = super(TestUnmanageVM, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
cls.hypervisor = testClient.getHypervisorInfo()
cls._cleanup = []
# Get Zone, Domain and templates
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
cls.services['mode'] = cls.zone.networktype
cls.template = get_template(
cls.apiclient,
cls.zone.id,
cls.services["ostype"],
hypervisor=cls.hypervisor.lower()
)
if cls.template == FAILED:
assert False, "get_template() failed to return template with description %s" % cls.services["ostype"]
cls.hypervisorNotSupported = cls.hypervisor.lower() != "vmware"
cls.services["small"]["zoneid"] = cls.zone.id
cls.services["small"]["template"] = cls.template.id
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
domainid=cls.domain.id
)
cls.small_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["small"]
)
cls.network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["l2-network_offering"],
)
cls.network_offering.update(cls.apiclient, state='Enabled')
cls._cleanup = [
cls.small_offering,
cls.network_offering,
cls.account
]
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.services["network"]["networkoffering"] = self.network_offering.id
self.network = Network.create(
self.apiclient,
self.services["l2-network"],
zoneid=self.zone.id,
networkofferingid=self.network_offering.id
)
self.cleanup = [
self.network
]
@attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false")
@skipTestIf("hypervisorNotSupported")
def test_01_unmanage_vm_cycle(self):
"""
Test the following:
1. Deploy VM
2. Unmanage VM
3. Verify VM is not listed in CloudStack
4. Verify VM is listed as part of the unmanaged instances
5. Import VM
6. Destroy VM
"""
# 1 - Deploy VM
self.virtual_machine = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
templateid=self.template.id,
serviceofferingid=self.small_offering.id,
networkids=self.network.id,
zoneid=self.zone.id
)
vm_id = self.virtual_machine.id
vm_instance_name = self.virtual_machine.instancename
hostid = self.virtual_machine.hostid
hosts = Host.list(
self.apiclient,
id=hostid
)
host = hosts[0]
clusterid = host.clusterid
list_vm = list_virtual_machines(
self.apiclient,
id=vm_id
)
self.assertEqual(
isinstance(list_vm, list),
True,
"Check if virtual machine is present"
)
vm_response = list_vm[0]
self.assertEqual(
vm_response.state,
"Running",
"VM state should be running after deployment"
)
# 2 - Unmanage VM from CloudStack
self.virtual_machine.unmanage(self.apiclient)
list_vm = list_virtual_machines(
self.apiclient,
id=vm_id
)
self.assertEqual(
list_vm,
None,
"VM should not be listed"
)
unmanaged_vms = VirtualMachine.listUnmanagedInstances(
self.apiclient,
clusterid=clusterid,
name=vm_instance_name
)
self.assertEqual(
len(unmanaged_vms),
1,
"Unmanaged VMs matching instance name list size is 1"
)
unmanaged_vm = unmanaged_vms[0]
self.assertEqual(
unmanaged_vm.powerstate,
"PowerOn",
"Unmanaged VM is still running"
)

View File

@ -966,6 +966,20 @@ class VirtualMachine:
cmd.details[0]["memory"] = custommemory
return apiclient.scaleVirtualMachine(cmd)
def unmanage(self, apiclient):
"""Unmanage a VM from CloudStack (currently VMware only)"""
cmd = unmanageVirtualMachine.unmanageVirtualMachineCmd()
cmd.id = self.id
return apiclient.unmanageVirtualMachine(cmd)
@classmethod
def listUnmanagedInstances(cls, apiclient, clusterid, name = None):
"""List unmanaged VMs (currently VMware only)"""
cmd = listUnmanagedInstances.listUnmanagedInstancesCmd()
cmd.clusterid = clusterid
cmd.name = name
return apiclient.listUnmanagedInstances(cmd)
class Volume:
"""Manage Volume Life cycle