mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Destroyvm also removes volumes (#2793)
* Allow user to detach and delete volumes when destroyinh VMs * Minor code refactoring
This commit is contained in:
parent
9a4149e5dc
commit
525ddfb717
@ -79,6 +79,8 @@ public interface VolumeApiService {
|
|||||||
|
|
||||||
Volume attachVolumeToVM(AttachVolumeCmd command);
|
Volume attachVolumeToVM(AttachVolumeCmd command);
|
||||||
|
|
||||||
|
Volume detachVolumeViaDestroyVM(long vmId, long volumeId);
|
||||||
|
|
||||||
Volume detachVolumeFromVM(DetachVolumeCmd cmd);
|
Volume detachVolumeFromVM(DetachVolumeCmd cmd);
|
||||||
|
|
||||||
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
|
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
|
||||||
|
|||||||
@ -714,6 +714,7 @@ public class ApiConstants {
|
|||||||
public static final String STDERR = "stderr";
|
public static final String STDERR = "stderr";
|
||||||
public static final String EXITCODE = "exitcode";
|
public static final String EXITCODE = "exitcode";
|
||||||
public static final String TARGET_ID = "targetid";
|
public static final String TARGET_ID = "targetid";
|
||||||
|
public static final String VOLUME_IDS = "volumeids";
|
||||||
|
|
||||||
public enum HostDetails {
|
public enum HostDetails {
|
||||||
all, capacity, events, stats, min;
|
all, capacity, events, stats, min;
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import org.apache.cloudstack.api.Parameter;
|
|||||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||||
import org.apache.cloudstack.api.ServerApiException;
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||||
|
import org.apache.cloudstack.api.response.VolumeResponse;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
import org.apache.cloudstack.context.CallContext;
|
||||||
|
|
||||||
import com.cloud.event.EventTypes;
|
import com.cloud.event.EventTypes;
|
||||||
@ -63,6 +64,14 @@ public class DestroyVMCmd extends BaseAsyncCmd {
|
|||||||
since = "4.2.1")
|
since = "4.2.1")
|
||||||
private Boolean expunge;
|
private Boolean expunge;
|
||||||
|
|
||||||
|
@Parameter( name = ApiConstants.VOLUME_IDS,
|
||||||
|
type = CommandType.LIST,
|
||||||
|
collectionType = CommandType.UUID,
|
||||||
|
entityType = VolumeResponse.class,
|
||||||
|
description = "Comma separated list of UUIDs for volumes that will be deleted",
|
||||||
|
since = "4.12.0")
|
||||||
|
private List<Long> volumeIds;
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
/////////////////// Accessors ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -78,6 +87,10 @@ public class DestroyVMCmd extends BaseAsyncCmd {
|
|||||||
return expunge;
|
return expunge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Long> getVolumeIds() {
|
||||||
|
return volumeIds;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
/////////////// API Implementation///////////////////
|
/////////////// API Implementation///////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -1857,8 +1857,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) {
|
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume")
|
||||||
|
public Volume detachVolumeViaDestroyVM(long vmId, long volumeId) {
|
||||||
|
return orchestrateDetachVolumeFromVM(vmId, volumeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) {
|
||||||
Volume volume = _volsDao.findById(volumeId);
|
Volume volume = _volsDao.findById(volumeId);
|
||||||
VMInstanceVO vm = _vmInstanceDao.findById(vmId);
|
VMInstanceVO vm = _vmInstanceDao.findById(vmId);
|
||||||
|
|
||||||
@ -1869,7 +1873,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
|
|
||||||
if (hostId == null) {
|
if (hostId == null) {
|
||||||
hostId = vm.getLastHostId();
|
hostId = vm.getLastHostId();
|
||||||
|
|
||||||
HostVO host = _hostDao.findById(hostId);
|
HostVO host = _hostDao.findById(hostId);
|
||||||
|
|
||||||
if (host != null && host.getHypervisorType() == HypervisorType.VMware) {
|
if (host != null && host.getHypervisorType() == HypervisorType.VMware) {
|
||||||
|
|||||||
@ -39,12 +39,6 @@ import java.util.stream.Collectors;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
|
||||||
import org.apache.commons.collections.MapUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
|
||||||
import com.cloud.user.AccountVO;
|
|
||||||
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
|
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
|
||||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||||
import org.apache.cloudstack.affinity.AffinityGroupService;
|
import org.apache.cloudstack.affinity.AffinityGroupService;
|
||||||
@ -97,6 +91,10 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
|||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
||||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.commons.collections.MapUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import com.cloud.agent.AgentManager;
|
import com.cloud.agent.AgentManager;
|
||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
@ -256,6 +254,7 @@ import com.cloud.template.VirtualMachineTemplate;
|
|||||||
import com.cloud.user.Account;
|
import com.cloud.user.Account;
|
||||||
import com.cloud.user.AccountManager;
|
import com.cloud.user.AccountManager;
|
||||||
import com.cloud.user.AccountService;
|
import com.cloud.user.AccountService;
|
||||||
|
import com.cloud.user.AccountVO;
|
||||||
import com.cloud.user.ResourceLimitService;
|
import com.cloud.user.ResourceLimitService;
|
||||||
import com.cloud.user.SSHKeyPair;
|
import com.cloud.user.SSHKeyPair;
|
||||||
import com.cloud.user.SSHKeyPairVO;
|
import com.cloud.user.SSHKeyPairVO;
|
||||||
@ -517,6 +516,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
|
|
||||||
private static final ConfigKey<Boolean> EnableAdditionalVmConfig = new ConfigKey<>("Advanced", Boolean.class, "enable.additional.vm.configuration",
|
private static final ConfigKey<Boolean> EnableAdditionalVmConfig = new ConfigKey<>("Advanced", Boolean.class, "enable.additional.vm.configuration",
|
||||||
"false", "allow additional arbitrary configuration to vm", true, ConfigKey.Scope.Account);
|
"false", "allow additional arbitrary configuration to vm", true, ConfigKey.Scope.Account);
|
||||||
|
private static final ConfigKey<Boolean> VmDestroyForcestop = new ConfigKey<Boolean>("Advanced", Boolean.class, "vm.destroy.forcestop", "false",
|
||||||
|
"On destroy, force-stop takes this value ", true);
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserVmVO getVirtualMachine(long vmId) {
|
public UserVmVO getVirtualMachine(long vmId) {
|
||||||
@ -2757,10 +2759,15 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
// check if VM exists
|
// check if VM exists
|
||||||
UserVmVO vm = _vmDao.findById(vmId);
|
UserVmVO vm = _vmDao.findById(vmId);
|
||||||
|
|
||||||
if (vm == null) {
|
if (vm == null || vm.getRemoved() != null) {
|
||||||
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
|
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vm.getState() == State.Destroyed || vm.getState() == State.Expunging) {
|
||||||
|
s_logger.debug("Vm id=" + vmId + " is already destroyed");
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
// check if there are active volume snapshots tasks
|
// check if there are active volume snapshots tasks
|
||||||
s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId);
|
s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId);
|
||||||
if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) {
|
if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) {
|
||||||
@ -2768,6 +2775,15 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
}
|
}
|
||||||
s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vmId);
|
s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vmId);
|
||||||
|
|
||||||
|
List<VolumeVO> volumes = getVolumesFromIds(cmd);
|
||||||
|
|
||||||
|
checkForUnattachedVolumes(vmId, volumes);
|
||||||
|
validateVolumes(volumes);
|
||||||
|
|
||||||
|
stopVirtualMachine(vmId, VmDestroyForcestop.value());
|
||||||
|
|
||||||
|
detachVolumesFromVm(volumes);
|
||||||
|
|
||||||
UserVm destroyedVm = destroyVm(vmId, expunge);
|
UserVm destroyedVm = destroyVm(vmId, expunge);
|
||||||
if (expunge) {
|
if (expunge) {
|
||||||
if (!expunge(vm, ctx.getCallingUserId(), ctx.getCallingAccount())) {
|
if (!expunge(vm, ctx.getCallingUserId(), ctx.getCallingAccount())) {
|
||||||
@ -2775,9 +2791,26 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteVolumesFromVm(volumes);
|
||||||
|
|
||||||
return destroyedVm;
|
return destroyedVm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<VolumeVO> getVolumesFromIds(DestroyVMCmd cmd) {
|
||||||
|
List<VolumeVO> volumes = new ArrayList<>();
|
||||||
|
if (cmd.getVolumeIds() != null) {
|
||||||
|
for (Long volId : cmd.getVolumeIds()) {
|
||||||
|
VolumeVO vol = _volsDao.findById(volId);
|
||||||
|
|
||||||
|
if (vol == null) {
|
||||||
|
throw new InvalidParameterValueException("Unable to find volume with ID: " + volId);
|
||||||
|
}
|
||||||
|
volumes.add(vol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return volumes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DB
|
@DB
|
||||||
public InstanceGroupVO createVmGroup(CreateVMGroupCmd cmd) {
|
public InstanceGroupVO createVmGroup(CreateVMGroupCmd cmd) {
|
||||||
@ -6518,4 +6551,52 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkForUnattachedVolumes(long vmId, List<VolumeVO> volumes) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (VolumeVO volume : volumes) {
|
||||||
|
if (volume.getInstanceId() == null || vmId != volume.getInstanceId()) {
|
||||||
|
sb.append(volume.toString() + "; ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(sb.toString())) {
|
||||||
|
throw new InvalidParameterValueException("The following supplied volumes are not attached to the VM: " + sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateVolumes(List<VolumeVO> volumes) {
|
||||||
|
|
||||||
|
for (VolumeVO volume : volumes) {
|
||||||
|
if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) {
|
||||||
|
throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void detachVolumesFromVm(List<VolumeVO> volumes) {
|
||||||
|
|
||||||
|
for (VolumeVO volume : volumes) {
|
||||||
|
|
||||||
|
Volume detachResult = _volumeService.detachVolumeViaDestroyVM(volume.getInstanceId(), volume.getId());
|
||||||
|
|
||||||
|
if (detachResult == null) {
|
||||||
|
s_logger.error("DestroyVM remove volume - failed to detach and delete volume " + volume.getInstanceId() + " from instance " + volume.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteVolumesFromVm(List<VolumeVO> volumes) {
|
||||||
|
|
||||||
|
for (VolumeVO volume : volumes) {
|
||||||
|
|
||||||
|
boolean deleteResult = _volumeService.deleteVolume(volume.getId(), CallContext.current().getCallingAccount());
|
||||||
|
|
||||||
|
if (!deleteResult) {
|
||||||
|
s_logger.error("DestroyVM remove volume - failed to delete volume " + volume.getInstanceId() + " from instance " + volume.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -32,7 +32,9 @@ from marvin.lib.base import (Account,
|
|||||||
Host,
|
Host,
|
||||||
Iso,
|
Iso,
|
||||||
Router,
|
Router,
|
||||||
Configurations)
|
Configurations,
|
||||||
|
Volume,
|
||||||
|
DiskOffering)
|
||||||
from marvin.lib.common import (get_domain,
|
from marvin.lib.common import (get_domain,
|
||||||
get_zone,
|
get_zone,
|
||||||
get_template,
|
get_template,
|
||||||
@ -786,6 +788,43 @@ class TestVMLifeCycle(cloudstackTestCase):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
|
||||||
|
def test_11_destroy_vm_and_volumes(self):
|
||||||
|
"""Test destroy Virtual Machine and it's volumes
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Validate the following
|
||||||
|
# 1. Deploys a VM and attaches disks to it
|
||||||
|
# 2. Destroys the VM with DataDisks option
|
||||||
|
|
||||||
|
small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]
|
||||||
|
|
||||||
|
small_virtual_machine = VirtualMachine.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.services["small"],
|
||||||
|
accountid=self.account.name,
|
||||||
|
domainid=self.account.domainid,
|
||||||
|
serviceofferingid=self.small_offering.id,
|
||||||
|
mode=self.services["mode"]
|
||||||
|
)
|
||||||
|
vol1 = Volume.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.services,
|
||||||
|
account=self.account.name,
|
||||||
|
diskofferingid=small_disk_offering.id,
|
||||||
|
domainid=self.account.domainid,
|
||||||
|
zoneid=self.zone.id
|
||||||
|
)
|
||||||
|
|
||||||
|
small_virtual_machine.attach_volume(self.apiclient, vol1)
|
||||||
|
|
||||||
|
self.debug("Destroy VM - ID: %s" % small_virtual_machine.id)
|
||||||
|
small_virtual_machine.delete(self.apiclient, volumeIds=vol1.id)
|
||||||
|
|
||||||
|
self.assertEqual(VirtualMachine.list(self.apiclient, id=small_virtual_machine.id), None, "List response contains records when it should not")
|
||||||
|
|
||||||
|
self.assertEqual(Volume.list(self.apiclient, id=vol1.id), None, "List response contains records when it should not")
|
||||||
|
|
||||||
class TestSecuredVmMigration(cloudstackTestCase):
|
class TestSecuredVmMigration(cloudstackTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -4179,6 +4179,15 @@ textarea {
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-dialog div.form-container div.value label {
|
||||||
|
display: block;
|
||||||
|
width: 119px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
.ui-dialog div.form-container div.value input.hasDatepicker {
|
.ui-dialog div.form-container div.value input.hasDatepicker {
|
||||||
color: #2F5D86;
|
color: #2F5D86;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@ -640,6 +640,7 @@ var dictionary = {
|
|||||||
"label.delete.secondary.staging.store":"Delete Secondary Staging Store",
|
"label.delete.secondary.staging.store":"Delete Secondary Staging Store",
|
||||||
"label.delete.sslcertificate":"Delete SSL Certificate",
|
"label.delete.sslcertificate":"Delete SSL Certificate",
|
||||||
"label.delete.ucs.manager":"Delete UCS Manager",
|
"label.delete.ucs.manager":"Delete UCS Manager",
|
||||||
|
"label.delete.volumes":"Volumes to be deleted",
|
||||||
"label.delete.vpn.user":"Delete VPN user",
|
"label.delete.vpn.user":"Delete VPN user",
|
||||||
"label.deleting.failed":"Deleting Failed",
|
"label.deleting.failed":"Deleting Failed",
|
||||||
"label.deleting.processing":"Deleting....",
|
"label.deleting.processing":"Deleting....",
|
||||||
@ -1813,6 +1814,8 @@ var dictionary = {
|
|||||||
"label.volgroup":"Volume Group",
|
"label.volgroup":"Volume Group",
|
||||||
"label.volume":"Volume",
|
"label.volume":"Volume",
|
||||||
"label.volume.details":"Volume details",
|
"label.volume.details":"Volume details",
|
||||||
|
"label.volume.empty":"No volumes attached to this VM",
|
||||||
|
"label.volume.ids":"Volume ID's",
|
||||||
"label.volume.limits":"Volume Limits",
|
"label.volume.limits":"Volume Limits",
|
||||||
"label.volume.migrated":"Volume migrated",
|
"label.volume.migrated":"Volume migrated",
|
||||||
"label.volume.name":"Volume Name",
|
"label.volume.name":"Volume Name",
|
||||||
|
|||||||
@ -112,6 +112,34 @@
|
|||||||
label: 'label.expunge',
|
label: 'label.expunge',
|
||||||
isBoolean: true,
|
isBoolean: true,
|
||||||
isChecked: false
|
isChecked: false
|
||||||
|
},
|
||||||
|
volumes: {
|
||||||
|
label: 'label.delete.volumes',
|
||||||
|
isBoolean: true,
|
||||||
|
isChecked: false
|
||||||
|
},
|
||||||
|
volumeids: {
|
||||||
|
label: 'label.volume.ids',
|
||||||
|
dependsOn: 'volumes',
|
||||||
|
isBoolean: true,
|
||||||
|
isHidden: true,
|
||||||
|
emptyMessage: 'label.volume.empty',
|
||||||
|
multiDataArray: true,
|
||||||
|
multiData: function(args) {
|
||||||
|
$.ajax({
|
||||||
|
url: createURL("listVolumes&virtualMachineId=" + args.context.instances[0].id) + "&type=DATADISK",
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var volumes = json.listvolumesresponse.volume;
|
||||||
|
args.response.success({
|
||||||
|
descriptionField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
data: volumes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -126,6 +154,26 @@
|
|||||||
expunge: true
|
expunge: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (args.data.volumes == 'on') {
|
||||||
|
|
||||||
|
var regex = RegExp('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}');
|
||||||
|
|
||||||
|
var selectedVolumes = [];
|
||||||
|
|
||||||
|
for (var key in args.data) {
|
||||||
|
var matches = key.match(regex);
|
||||||
|
|
||||||
|
if (matches != null) {
|
||||||
|
selectedVolumes.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend(data, {
|
||||||
|
volumeids: $(selectedVolumes).map(function(index, volume) {
|
||||||
|
return volume;
|
||||||
|
}).toArray().join(',')
|
||||||
|
});
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: createURL('destroyVirtualMachine'),
|
url: createURL('destroyVirtualMachine'),
|
||||||
data: data,
|
data: data,
|
||||||
|
|||||||
@ -433,6 +433,68 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
} else if (field.multiDataArray) {
|
||||||
|
|
||||||
|
$input = $('<div>');
|
||||||
|
|
||||||
|
multiArgs = {
|
||||||
|
context: args.context,
|
||||||
|
response: {
|
||||||
|
success: function(args) {
|
||||||
|
if (args.data == undefined || args.data.length == 0) {
|
||||||
|
|
||||||
|
var label = field.emptyMessage != null ? field.emptyMessage : 'No data available';
|
||||||
|
|
||||||
|
$input
|
||||||
|
.addClass('value')
|
||||||
|
.appendTo($value)
|
||||||
|
.append(
|
||||||
|
$('<label>').html(_l(label))
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$input.addClass('multi-array').addClass(key).appendTo($value);
|
||||||
|
|
||||||
|
$(args.data).each(function() {
|
||||||
|
|
||||||
|
var id;
|
||||||
|
if (field.valueField)
|
||||||
|
id = this[field.valueField];
|
||||||
|
else
|
||||||
|
id = this.id !== undefined ? this.id : this.name;
|
||||||
|
|
||||||
|
var desc;
|
||||||
|
if (args.descriptionField)
|
||||||
|
desc = this[args.descriptionField];
|
||||||
|
else
|
||||||
|
desc = _l(this.description);
|
||||||
|
|
||||||
|
$input.append(
|
||||||
|
$('<div>')
|
||||||
|
.addClass('item')
|
||||||
|
.append(
|
||||||
|
$.merge(
|
||||||
|
$('<div>').addClass('name').html(_l(desc)),
|
||||||
|
$('<div>').addClass('value').append(
|
||||||
|
$('<input>').attr({
|
||||||
|
name: id,
|
||||||
|
type: 'checkbox'
|
||||||
|
})
|
||||||
|
.data('json-obj', this)
|
||||||
|
.appendTo($value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multiFn = field.multiData;
|
||||||
|
multiFn(multiArgs);
|
||||||
} else {
|
} else {
|
||||||
$input = $('<input>').attr({
|
$input = $('<input>').attr({
|
||||||
name: key,
|
name: key,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user