secondary storage resource limit for download

This commit is contained in:
abh1sar 2026-03-02 11:06:13 +05:30 committed by Daan Hoogland
parent 4bcd509193
commit 03dfe4d1f3
12 changed files with 173 additions and 21 deletions

View File

@ -23,9 +23,10 @@ import org.apache.cloudstack.api.InternalIdentity;
public interface VMTemplateStorageResourceAssoc extends InternalIdentity {
public static enum Status {
UNKNOWN, DOWNLOAD_ERROR, NOT_DOWNLOADED, DOWNLOAD_IN_PROGRESS, DOWNLOADED, ABANDONED, UPLOADED, NOT_UPLOADED, UPLOAD_ERROR, UPLOAD_IN_PROGRESS, CREATING, CREATED, BYPASSED
UNKNOWN, DOWNLOAD_ERROR, NOT_DOWNLOADED, DOWNLOAD_IN_PROGRESS, DOWNLOADED, ABANDONED, LIMIT_REACHED, UPLOADED, NOT_UPLOADED, UPLOAD_ERROR, UPLOAD_IN_PROGRESS, CREATING, CREATED, BYPASSED
}
List<Status> ERROR_DOWNLOAD_STATES = List.of(Status.DOWNLOAD_ERROR, Status.ABANDONED, Status.LIMIT_REACHED, Status.UNKNOWN);
List<Status> PENDING_DOWNLOAD_STATES = List.of(Status.NOT_DOWNLOADED, Status.DOWNLOAD_IN_PROGRESS);
String getInstallPath();

View File

@ -140,7 +140,7 @@ public class DownloadAnswer extends Answer {
}
public Long getTemplateSize() {
return templateSize;
return templateSize == 0 ? templatePhySicalSize : templateSize;
}
public void setTemplatePhySicalSize(long templatePhySicalSize) {

View File

@ -230,8 +230,10 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
updateBuilder.setJobId(answer.getJobId());
updateBuilder.setLocalDownloadPath(answer.getDownloadPath());
updateBuilder.setInstallPath(answer.getInstallPath());
updateBuilder.setSize(answer.getTemplateSize());
updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize());
if (!VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) {
updateBuilder.setSize(answer.getTemplateSize());
updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize());
}
_templateStoreDao.update(tmpltStoreVO.getId(), updateBuilder);
// update size in vm_template table
VMTemplateVO tmlptUpdater = _templateDao.createForUpdate();
@ -241,8 +243,7 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
AsyncCompletionCallback<CreateCmdResult> caller = context.getParentCallback();
if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR ||
answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.ABANDONED || answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.UNKNOWN) {
if (VMTemplateStorageResourceAssoc.ERROR_DOWNLOAD_STATES.contains(answer.getDownloadStatus())) {
CreateCmdResult result = new CreateCmdResult(null, null);
result.setSuccess(false);
result.setResult(answer.getErrorString());

View File

@ -258,7 +258,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
templateSizeSearch = _vmTemplateStoreDao.createSearchBuilder(SumCount.class);
templateSizeSearch.select("sum", Func.SUM, templateSizeSearch.entity().getSize());
templateSizeSearch.and("downloadState", templateSizeSearch.entity().getDownloadState(), Op.EQ);
templateSizeSearch.and("downloadState", templateSizeSearch.entity().getDownloadState(), Op.IN);
templateSizeSearch.and("destroyed", templateSizeSearch.entity().getDestroyed(), Op.EQ);
SearchBuilder<VMTemplateVO> join1 = _vmTemplateDao.createSearchBuilder();
join1.and("accountId", join1.entity().getAccountId(), Op.EQ);
@ -1410,7 +1410,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
long totalTemplatesSize = 0;
SearchCriteria<SumCount> sc = templateSizeSearch.create();
sc.setParameters("downloadState", Status.DOWNLOADED);
sc.setParameters("downloadState", Status.DOWNLOADED, Status.DOWNLOAD_IN_PROGRESS);
sc.setParameters("destroyed", false);
sc.setJoinParameters("templates", "accountId", accountId);
List<SumCount> templates = _vmTemplateStoreDao.customSearch(sc, null);

View File

@ -95,6 +95,11 @@ public abstract class DownloadActiveState extends DownloadState {
return Status.ABANDONED.toString();
}
@Override
public String handleLimitReached() {
return Status.LIMIT_REACHED.toString();
}
@Override
public String handleDisconnect() {

View File

@ -60,6 +60,11 @@ public class DownloadErrorState extends DownloadInactiveState {
return Status.ABANDONED.toString();
}
@Override
public String handleLimitReached() {
return Status.LIMIT_REACHED.toString();
}
@Override
public String getName() {
return Status.DOWNLOAD_ERROR.toString();

View File

@ -36,6 +36,12 @@ public abstract class DownloadInactiveState extends DownloadState {
return getName();
}
@Override
public String handleLimitReached() {
// ignore and stay put
return getName();
}
@Override
public String handleDisconnect() {
//ignore and stay put

View File

@ -0,0 +1,54 @@
// 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.storage.download;
import org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType;
import org.apache.logging.log4j.Level;
import com.cloud.agent.api.storage.DownloadAnswer;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
public class DownloadLimitReachedState extends DownloadInactiveState {
public DownloadLimitReachedState(DownloadListener dl) {
super(dl);
}
@Override
public String getName() {
return Status.LIMIT_REACHED.toString();
}
@Override
public String handleEvent(DownloadEvent event, Object eventObj) {
if (logger.isTraceEnabled()) {
getDownloadListener().log("handleEvent, event type=" + event + ", curr state=" + getName(), Level.TRACE);
}
return Status.LIMIT_REACHED.toString();
}
@Override
public void onEntry(String prevState, DownloadEvent event, Object evtObj) {
if (!prevState.equalsIgnoreCase(getName())) {
DownloadAnswer answer = new DownloadAnswer("Storage Limit Reached", Status.LIMIT_REACHED);
getDownloadListener().callback(answer);
getDownloadListener().cancelStatusTask();
getDownloadListener().cancelTimeoutTask();
getDownloadListener().scheduleImmediateStatusCheck(RequestType.PURGE);
}
}
}

View File

@ -25,6 +25,15 @@ import java.util.Timer;
import javax.inject.Inject;
import com.cloud.configuration.Resource;
import com.cloud.resourcelimit.CheckedReservation;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.ResourceLimitService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
@ -34,10 +43,13 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
import org.apache.cloudstack.reservation.dao.ReservationDao;
import org.apache.cloudstack.storage.command.DownloadCommand;
import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
import org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.utils.cache.LazyCache;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
@ -107,6 +119,7 @@ public class DownloadListener implements Listener {
public static final String DOWNLOAD_ERROR = Status.DOWNLOAD_ERROR.toString();
public static final String DOWNLOAD_IN_PROGRESS = Status.DOWNLOAD_IN_PROGRESS.toString();
public static final String DOWNLOAD_ABANDONED = Status.ABANDONED.toString();
public static final String DOWNLOAD_LIMIT_REACHED = Status.LIMIT_REACHED.toString();
private EndPoint _ssAgent;
@ -137,6 +150,18 @@ public class DownloadListener implements Listener {
private DataStoreManager _storeMgr;
@Inject
private VolumeService _volumeSrv;
@Inject
private VMTemplateDao _templateDao;
@Inject
private TemplateDataStoreDao _templateDataStoreDao;
@Inject
private VolumeDao _volumeDao;
@Inject
private ResourceLimitService _resourceLimitMgr;
@Inject
private AccountManager _accountMgr;
@Inject
ReservationDao _reservationDao;
private LazyCache<Long, List<Hypervisor.HypervisorType>> zoneHypervisorsCache;
@ -160,7 +185,7 @@ public class DownloadListener implements Listener {
_downloadMonitor = downloadMonitor;
_cmd = cmd;
initStateMachine();
_currState = getState(Status.NOT_DOWNLOADED.toString());
_currState = getState(NOT_DOWNLOADED);
this._timer = timer;
_timeoutTask = new TimeoutTask(this);
this._timer.schedule(_timeoutTask, 3 * STATUS_POLL_INTERVAL);
@ -184,11 +209,12 @@ public class DownloadListener implements Listener {
}
private void initStateMachine() {
_stateMap.put(Status.NOT_DOWNLOADED.toString(), new NotDownloadedState(this));
_stateMap.put(Status.DOWNLOADED.toString(), new DownloadCompleteState(this));
_stateMap.put(Status.DOWNLOAD_ERROR.toString(), new DownloadErrorState(this));
_stateMap.put(Status.DOWNLOAD_IN_PROGRESS.toString(), new DownloadInProgressState(this));
_stateMap.put(Status.ABANDONED.toString(), new DownloadAbandonedState(this));
_stateMap.put(NOT_DOWNLOADED, new NotDownloadedState(this));
_stateMap.put(DOWNLOADED, new DownloadCompleteState(this));
_stateMap.put(DOWNLOAD_ERROR, new DownloadErrorState(this));
_stateMap.put(DOWNLOAD_IN_PROGRESS, new DownloadInProgressState(this));
_stateMap.put(DOWNLOAD_ABANDONED, new DownloadAbandonedState(this));
_stateMap.put(DOWNLOAD_LIMIT_REACHED, new DownloadLimitReachedState(this));
}
private DownloadState getState(String stateName) {
@ -239,10 +265,53 @@ public class DownloadListener implements Listener {
return false;
}
private Long getAccountIdForDataObject() {
if (object == null) {
return null;
}
if (DataObjectType.TEMPLATE.equals(object.getType())) {
VMTemplateVO t = _templateDao.findById(object.getId());
return t != null ? t.getAccountId() : null;
} else if (DataObjectType.VOLUME.equals(object.getType())) {
VolumeVO v = _volumeDao.findById(object.getId());
return v != null ? v.getAccountId() : null;
}
return null;
}
private Long getSizeFromDB() {
Long lastSize = 0L;
if (DataObjectType.TEMPLATE.equals(object.getType())) {
TemplateDataStoreVO t = _templateDataStoreDao.findByStoreTemplate(object.getDataStore().getId(), object.getId());
lastSize = t.getSize();
} else if (DataObjectType.VOLUME.equals(object.getType())) {
VolumeVO v = _volumeDao.findById(object.getId());
lastSize = v.getSize();
}
return lastSize;
}
private Boolean checkAndUpdateResourceLimits(DownloadAnswer answer) {
Long lastSize = getSizeFromDB();
Long currentSize = answer.getTemplateSize();
if (currentSize > lastSize) {
Long accountId = getAccountIdForDataObject();
Account account = _accountMgr.getAccount(accountId);
Long usage = currentSize - lastSize;
try (CheckedReservation secStorageReservation = new CheckedReservation(account, Resource.ResourceType.secondary_storage, usage, _reservationDao, _resourceLimitMgr)) {
_resourceLimitMgr.incrementResourceCount(accountId, Resource.ResourceType.secondary_storage, usage);
} catch (Exception e) {
return false;
}
}
return true;
}
@Override
public boolean processAnswers(long agentId, long seq, Answer[] answers) {
boolean processed = false;
if (answers != null & answers.length > 0) {
if (answers != null && answers.length > 0) {
if (answers[0] instanceof DownloadAnswer) {
final DownloadAnswer answer = (DownloadAnswer)answers[0];
if (getJobId() == null) {
@ -250,7 +319,11 @@ public class DownloadListener implements Listener {
} else if (!getJobId().equalsIgnoreCase(answer.getJobId())) {
return false;//TODO
}
transition(DownloadEvent.DOWNLOAD_ANSWER, answer);
if (!checkAndUpdateResourceLimits(answer)) {
transition(DownloadEvent.LIMIT_REACHED, answer);
} else {
transition(DownloadEvent.DOWNLOAD_ANSWER, answer);
}
processed = true;
}
}

View File

@ -26,7 +26,7 @@ import com.cloud.agent.api.storage.DownloadAnswer;
public abstract class DownloadState {
public static enum DownloadEvent {
DOWNLOAD_ANSWER, ABANDON_DOWNLOAD, TIMEOUT_CHECK, DISCONNECT
DOWNLOAD_ANSWER, ABANDON_DOWNLOAD, LIMIT_REACHED, TIMEOUT_CHECK, DISCONNECT
};
protected Logger logger = LogManager.getLogger(getClass());
@ -51,6 +51,8 @@ public abstract class DownloadState {
return handleAnswer(answer);
case ABANDON_DOWNLOAD:
return handleAbort();
case LIMIT_REACHED:
return handleLimitReached();
case TIMEOUT_CHECK:
Date now = new Date();
long update = now.getTime() - dl.getLastUpdated().getTime();
@ -78,6 +80,8 @@ public abstract class DownloadState {
public abstract String handleAbort();
public abstract String handleLimitReached();
public abstract String handleDisconnect();
public abstract String handleAnswer(DownloadAnswer answer);

View File

@ -481,13 +481,12 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
CreateTemplateContext<TemplateApiResult> context) {
TemplateApiResult result = callback.getResult();
TemplateInfo template = context.template;
VMTemplateVO tmplt = _tmpltDao.findById(template.getId());
if (result.isSuccess()) {
VMTemplateVO tmplt = _tmpltDao.findById(template.getId());
// need to grant permission for public templates
if (tmplt.isPublicTemplate()) {
_messageBus.publish(_name, TemplateManager.MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT, PublishScope.LOCAL, tmplt.getId());
}
long accountId = tmplt.getAccountId();
if (template.getSize() != null) {
// publish usage event
String etype = EventTypes.EVENT_TEMPLATE_CREATE;
@ -517,7 +516,11 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
template.getSize(), VirtualMachineTemplate.class.getName(), template.getUuid());
}
}
_resourceLimitMgr.recalculateResourceCount(accountId, tmplt.getDomainId(), ResourceType.secondary_storage.getOrdinal());
}
if (tmplt != null) {
long accountId = tmplt.getAccountId();
Account account = _accountDao.findById(accountId);
_resourceLimitMgr.recalculateResourceCount(accountId, account.getDomainId(), ResourceType.secondary_storage.getOrdinal());
}
return null;

View File

@ -996,7 +996,7 @@ public class DownloadManagerImpl extends ManagerBase implements DownloadManager
break; // TODO
}
return new DownloadAnswer(jobId, getDownloadPct(jobId), getDownloadError(jobId), getDownloadStatus2(jobId), getDownloadLocalPath(jobId), getInstallPath(jobId),
getDownloadTemplateSize(jobId), getDownloadTemplatePhysicalSize(jobId), getDownloadCheckSum(jobId));
getDownloadTemplateSize(jobId), td.getDownloadedBytes(), getDownloadCheckSum(jobId));
}
private String getInstallPath(String jobId) {