Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/src/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class DeleteTemplateCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the ID of zone of the template")
private Long zoneId;

@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force delete a template.", since = "4.9+")
private Boolean forced;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand All @@ -64,6 +67,9 @@ public Long getZoneId() {
return zoneId;
}

public boolean isForced() {
return (forced != null) ? forced : true;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
8 changes: 8 additions & 0 deletions engine/schema/src/com/cloud/vm/dao/VMInstanceDao.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public interface VMInstanceDao extends GenericDao<VMInstanceVO, Long>, StateDao<
*/
List<VMInstanceVO> listByPodId(long podId);

/**
* Lists non-expunged VMs by templateId
* @param templateId
* @return list of VMInstanceVO deployed from the specified template, that are not expunged
*/
public List<VMInstanceVO> listNonExpungedByTemplate(long templateId);


/**
* Lists non-expunged VMs by zone ID and templateId
* @param zoneId
Expand Down
16 changes: 16 additions & 0 deletions engine/schema/src/com/cloud/vm/dao/VMInstanceDaoImpl.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
protected SearchBuilder<VMInstanceVO> IdStatesSearch;
protected SearchBuilder<VMInstanceVO> AllFieldsSearch;
protected SearchBuilder<VMInstanceVO> ZoneTemplateNonExpungedSearch;
protected SearchBuilder<VMInstanceVO> TemplateNonExpungedSearch;
protected SearchBuilder<VMInstanceVO> NameLikeSearch;
protected SearchBuilder<VMInstanceVO> StateChangeSearch;
protected SearchBuilder<VMInstanceVO> TransitionSearch;
Expand Down Expand Up @@ -165,6 +166,12 @@ protected void init() {
ZoneTemplateNonExpungedSearch.and("state", ZoneTemplateNonExpungedSearch.entity().getState(), Op.NEQ);
ZoneTemplateNonExpungedSearch.done();


TemplateNonExpungedSearch = createSearchBuilder();
TemplateNonExpungedSearch.and("template", TemplateNonExpungedSearch.entity().getTemplateId(), Op.EQ);
TemplateNonExpungedSearch.and("state", TemplateNonExpungedSearch.entity().getState(), Op.NEQ);
TemplateNonExpungedSearch.done();

NameLikeSearch = createSearchBuilder();
NameLikeSearch.and("name", NameLikeSearch.entity().getHostName(), Op.LIKE);
NameLikeSearch.done();
Expand Down Expand Up @@ -334,6 +341,15 @@ public List<VMInstanceVO> listByZoneIdAndType(long zoneId, VirtualMachine.Type t
return listBy(sc);
}

@Override
public List<VMInstanceVO> listNonExpungedByTemplate(long templateId) {
SearchCriteria<VMInstanceVO> sc = TemplateNonExpungedSearch.create();

sc.setParameters("template", templateId);
sc.setParameters("state", State.Expunging);
return listBy(sc);
}

@Override
public List<VMInstanceVO> listNonExpungedByZoneAndTemplate(long zoneId, long templateId) {
SearchCriteria<VMInstanceVO> sc = ZoneTemplateNonExpungedSearch.create();
Expand Down
14 changes: 14 additions & 0 deletions server/src/com/cloud/template/TemplateManagerImpl.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.EnumUtils;
import com.google.common.base.Joiner;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

Expand Down Expand Up @@ -1235,6 +1236,19 @@ public boolean deleteTemplate(DeleteTemplateCmd cmd) {
throw new InvalidParameterValueException("unable to find template with id " + templateId);
}

List<VMInstanceVO> vmInstanceVOList;
if(cmd.getZoneId() != null) {
vmInstanceVOList = _vmInstanceDao.listNonExpungedByZoneAndTemplate(cmd.getZoneId(), templateId);
}
else {
vmInstanceVOList = _vmInstanceDao.listNonExpungedByTemplate(templateId);
}
if(!cmd.isForced() && CollectionUtils.isNotEmpty(vmInstanceVOList)) {
final String message = String.format("Unable to delete template with id: %1$s because VM instances: [%2$s] are using it.", templateId, Joiner.on(",").join(vmInstanceVOList));
s_logger.warn(message);
throw new InvalidParameterValueException(message);
}

_accountMgr.checkAccess(caller, AccessType.OperateEntry, true, template);

if (template.getFormat() == ImageFormat.ISO) {
Expand Down
55 changes: 55 additions & 0 deletions server/test/com/cloud/template/TemplateManagerImplTest.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.Storage;
import com.cloud.storage.TemplateProfile;
import com.cloud.projects.ProjectManager;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Snapshot;
Expand Down Expand Up @@ -59,9 +61,11 @@
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd;
import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
Expand Down Expand Up @@ -169,6 +173,13 @@ public class TemplateManagerImplTest {
@Inject
StorageStrategyFactory storageStrategyFactory;

@Inject
VMInstanceDao _vmInstanceDao;

@Inject
private VMTemplateDao _tmpltDao;


public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
AtomicInteger ai = new AtomicInteger(0);
public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
Expand Down Expand Up @@ -216,6 +227,50 @@ public void testVerifyTemplateIdOfNonSystemTemplate() {
templateManager.verifyTemplateId(1L);
}

@Test
public void testForceDeleteTemplate() {
//In this Unit test all the conditions related to "force template delete flag" are tested.

DeleteTemplateCmd cmd = mock(DeleteTemplateCmd.class);
VMTemplateVO template = mock(VMTemplateVO.class);
TemplateAdapter templateAdapter = mock(TemplateAdapter.class);
TemplateProfile templateProfile = mock(TemplateProfile.class);


List<VMInstanceVO> vmInstanceVOList = new ArrayList<VMInstanceVO>();
List<TemplateAdapter> adapters = new ArrayList<TemplateAdapter>();
adapters.add(templateAdapter);
when(cmd.getId()).thenReturn(0L);
when(_tmpltDao.findById(cmd.getId())).thenReturn(template);
when(cmd.getZoneId()).thenReturn(null);

when(template.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None);
when(template.getFormat()).thenReturn(Storage.ImageFormat.VMDK);
templateManager.setTemplateAdapters(adapters);
when(templateAdapter.getName()).thenReturn(TemplateAdapter.TemplateAdapterType.Hypervisor.getName().toString());
when(templateAdapter.prepareDelete(any(DeleteTemplateCmd.class))).thenReturn(templateProfile);
when(templateAdapter.delete(templateProfile)).thenReturn(true);

//case 1: When Force delete flag is 'true' but VM instance VO list is empty.
when(cmd.isForced()).thenReturn(true);
templateManager.deleteTemplate(cmd);

//case 2.1: When Force delete flag is 'false' and VM instance VO list is empty.
when(cmd.isForced()).thenReturn(false);
templateManager.deleteTemplate(cmd);

//case 2.2: When Force delete flag is 'false' and VM instance VO list is non empty.
when(cmd.isForced()).thenReturn(false);
VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
when(vmInstanceVO.getInstanceName()).thenReturn("mydDummyVM");
vmInstanceVOList.add(vmInstanceVO);
when(_vmInstanceDao.listNonExpungedByTemplate(anyLong())).thenReturn(vmInstanceVOList);
try {
templateManager.deleteTemplate(cmd);
} catch(Exception e) {
assertTrue("Invalid Parameter Value Exception is expected", (e instanceof InvalidParameterValueException));
}
}
@Test
public void testPrepareTemplateIsSeeded() {
VMTemplateVO mockTemplate = mock(VMTemplateVO.class);
Expand Down
26 changes: 18 additions & 8 deletions ui/scripts/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -1526,14 +1526,24 @@
actions: {
remove: {
label: 'label.action.delete.template',
createForm: {
title: 'label.action.delete.template',
desc: function(args) {
if(args.context.templates[0].crossZones == true) {
return 'message.action.delete.template.for.all.zones';
} else {
return 'message.action.delete.template';
}
},
fields: {
forced: {
label: 'force.delete',
isBoolean: true,
isChecked: false
}
}
},
messages: {
confirm: function(args) {
if(args.context.templates[0].crossZones == true) {
return 'message.action.delete.template.for.all.zones';
} else {
return 'message.action.delete.template';
}
},
notification: function(args) {
return 'label.action.delete.template';
}
Expand All @@ -1544,7 +1554,7 @@
queryParams += "&zoneid=" + args.context.zones[0].zoneid;
}
$.ajax({
url: createURL(queryParams),
url: createURL(queryParams + "&forced=" + (args.data.forced == "on")),
dataType: "json",
async: true,
success: function(json) {
Expand Down