mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
[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:
parent
3ede1eaa49
commit
8c1d749360
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -58,4 +58,6 @@ public interface VirtualMachineGuru {
|
||||
* @return
|
||||
*/
|
||||
void prepareStop(VirtualMachineProfile profile);
|
||||
|
||||
void finalizeUnmanage(VirtualMachine vm);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -93,4 +93,6 @@ public interface VolumeService {
|
||||
SnapshotInfo takeSnapshot(VolumeInfo volume);
|
||||
|
||||
VolumeInfo updateHypervisorSnapshotReserveForVolume(DiskOffering diskOffering, long volumeId, HypervisorType hyperType);
|
||||
|
||||
void unmanageVolume(long volumeId);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,4 +596,8 @@ public class ElasticLoadBalancerManagerImpl extends ManagerBase implements Elast
|
||||
public void prepareStop(VirtualMachineProfile profile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalizeUnmanage(VirtualMachine vm) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1496,6 +1496,10 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalizeUnmanage(VirtualMachine vm) {
|
||||
}
|
||||
|
||||
public List<SecondaryStorageVmAllocator> getSecondaryStorageVmAllocators() {
|
||||
return _ssVmAllocators;
|
||||
}
|
||||
|
||||
@ -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"
|
||||
)
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user