| // | |
| // 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.resourcelimit; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.stream.Collectors; | |
| import com.cloud.api.ApiDBUtils; | |
| import org.apache.cloudstack.context.CallContext; | |
| import org.apache.cloudstack.reservation.ReservationVO; | |
| import org.apache.cloudstack.reservation.dao.ReservationDao; | |
| import org.apache.cloudstack.resourcelimit.Reserver; | |
| import org.apache.cloudstack.user.ResourceReservation; | |
| import org.apache.commons.collections.CollectionUtils; | |
| import org.apache.commons.lang3.ObjectUtils; | |
| import org.apache.commons.lang3.StringUtils; | |
| import org.apache.logging.log4j.LogManager; | |
| import org.apache.logging.log4j.Logger; | |
| import org.jetbrains.annotations.NotNull; | |
| import com.cloud.configuration.Resource.ResourceType; | |
| import com.cloud.exception.ResourceAllocationException; | |
| import com.cloud.user.Account; | |
| import com.cloud.user.ResourceLimitService; | |
| import com.cloud.utils.db.GlobalLock; | |
| import com.cloud.utils.exception.CloudRuntimeException; | |
| public class CheckedReservation implements Reserver { | |
| protected Logger logger = LogManager.getLogger(getClass()); | |
| private static final int TRY_TO_GET_LOCK_TIME = 120; | |
| private GlobalLock quotaLimitLock; | |
| ReservationDao reservationDao; | |
| ResourceLimitService resourceLimitService; | |
| private Account account; | |
| private Long domainId; | |
| private ResourceType resourceType; | |
| private Long resourceId; | |
| private Long reservationAmount; | |
| private List<String> reservationTags; | |
| private Long existingAmount; | |
| private List<String> existingLimitTags; | |
| private List<ResourceReservation> reservations; | |
| private String getContextParameterKey() { | |
| return getResourceReservationContextParameterKey(resourceType); | |
| } | |
| public static String getResourceReservationContextParameterKey(final ResourceType type) { | |
| return String.format("%s-%s", ResourceReservation.class.getSimpleName(), type.getName()); | |
| } | |
| private void removeAllReservations() { | |
| if (CollectionUtils.isEmpty(reservations)) { | |
| return; | |
| } | |
| CallContext.current().removeContextParameter(getContextParameterKey()); | |
| for (ResourceReservation reservation : reservations) { | |
| reservationDao.remove(reservation.getId()); | |
| } | |
| this.reservations = null; | |
| } | |
| protected void checkLimitAndPersistReservations(Account account, Long domainId, ResourceType resourceType, Long resourceId, List<String> resourceLimitTags, Long amount) throws ResourceAllocationException { | |
| try { | |
| checkLimitAndPersistReservation(account, domainId, resourceType, resourceId, null, amount); | |
| if (CollectionUtils.isNotEmpty(resourceLimitTags)) { | |
| for (String tag : resourceLimitTags) { | |
| checkLimitAndPersistReservation(account, domainId, resourceType, resourceId, tag, amount); | |
| } | |
| } | |
| } catch (ResourceAllocationException rae) { | |
| removeAllReservations(); | |
| throw rae; | |
| } | |
| } | |
| protected void checkLimitAndPersistReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, String tag, Long amount) throws ResourceAllocationException { | |
| if (amount > 0) { | |
| resourceLimitService.checkResourceLimitWithTag(account, domainId, true, resourceType, tag, amount); | |
| } | |
| ReservationVO reservationVO = new ReservationVO(account.getAccountId(), domainId, resourceType, tag, amount); | |
| if (resourceId != null) { | |
| reservationVO.setResourceId(resourceId); | |
| } | |
| ResourceReservation reservation = reservationDao.persist(reservationVO); | |
| this.reservations.add(reservation); | |
| } | |
| // TODO: refactor these into a Builder to avoid having so many constructors | |
| public CheckedReservation(Account account, ResourceType resourceType, List<String> resourceLimitTags, Long reservationAmount, | |
| ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { | |
| this(account, resourceType, null, resourceLimitTags, null, reservationAmount, null, reservationDao, resourceLimitService); | |
| } | |
| public CheckedReservation(Account account, ResourceType resourceType, Long resourceId, List<String> reservedTags, | |
| List<String> existingTags, Long reservationAmount, Long existingAmount, ReservationDao reservationDao, | |
| ResourceLimitService resourceLimitService) throws ResourceAllocationException { | |
| this(account, null, resourceType, resourceId, reservedTags, existingTags, reservationAmount, existingAmount, reservationDao, resourceLimitService); | |
| } | |
| public CheckedReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, List<String> reservedTags, | |
| Long reservationAmount, ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { | |
| this(account, domainId, resourceType, resourceId, reservedTags, null, reservationAmount, null, reservationDao, resourceLimitService); | |
| } | |
| public CheckedReservation(Account account, ResourceType resourceType, Long resourceId, List<String> reservedTags, Long reservationAmount, ReservationDao reservationDao, ResourceLimitService resourceLimitService) throws ResourceAllocationException { | |
| this(account, null, resourceType, resourceId, reservedTags, null, reservationAmount, null, reservationDao, resourceLimitService); | |
| } | |
| /** | |
| * - check if adding a reservation is allowed | |
| * - create DB entry for reservation | |
| * - hold the id of this record as a ticket for implementation | |
| * | |
| * @param reservationAmount positive number of the resource type to reserve | |
| * @throws ResourceAllocationException | |
| */ | |
| public CheckedReservation(Account account, Long domainId, ResourceType resourceType, Long resourceId, List<String> reservedTags, | |
| List<String> existingTags, Long reservationAmount, Long existingAmount, ReservationDao reservationDao, | |
| ResourceLimitService resourceLimitService) throws ResourceAllocationException { | |
| if (ObjectUtils.allNull(account, domainId)) { | |
| logger.debug("Not reserving any {} resources, as no account/domain was provided.", resourceType); | |
| return; | |
| } | |
| this.reservationDao = reservationDao; | |
| this.resourceLimitService = resourceLimitService; | |
| // When allocating to a domain instead of a specific account, consider the system account as the owner for the validations here. | |
| if (account == null) { | |
| account = ApiDBUtils.getSystemAccount(); | |
| } | |
| this.account = account; | |
| if (domainId == null) { | |
| domainId = account.getDomainId(); | |
| } | |
| this.domainId = domainId; | |
| this.resourceType = resourceType; | |
| this.reservationAmount = reservationAmount; | |
| this.existingAmount = existingAmount; | |
| this.reservations = new ArrayList<>(); | |
| this.reservationTags = getTagsWithoutNull(reservedTags); | |
| this.existingLimitTags = getTagsWithoutNull(existingTags); | |
| // TODO: refactor me | |
| if (this.reservationAmount != null && this.reservationAmount != 0) { | |
| if (reservationAmount > 0) { | |
| setGlobalLock(); | |
| if (quotaLimitLock.lock(TRY_TO_GET_LOCK_TIME)) { | |
| try { | |
| adjustCountToNotConsiderExistingAmount(); | |
| checkLimitAndPersistReservations(account, this.domainId, resourceType, resourceId, reservationTags, reservationAmount); | |
| CallContext.current().putContextParameter(getContextParameterKey(), getIds()); | |
| } catch (NullPointerException npe) { | |
| throw new CloudRuntimeException("not enough means to check limits", npe); | |
| } finally { | |
| quotaLimitLock.unlock(); | |
| } | |
| } else { | |
| throw new ResourceAllocationException(String.format("unable to acquire resource reservation \"%s\"", quotaLimitLock.getName()), resourceType); | |
| } | |
| } else { | |
| checkLimitAndPersistReservations(account, this.domainId, resourceType, resourceId, reservationTags, reservationAmount); | |
| } | |
| } else { | |
| logger.debug("not reserving any amount of resources for {} in domain {}, type: {}, tag: {}", | |
| account.getAccountName(), this.domainId, resourceType, getResourceLimitTagsAsString()); | |
| } | |
| } | |
| protected List<String> getTagsWithoutNull(List<String> tags) { | |
| if (tags == null) { | |
| return null; | |
| } | |
| return tags.stream().filter(Objects::nonNull).collect(Collectors.toList()); | |
| } | |
| protected void adjustCountToNotConsiderExistingAmount() throws ResourceAllocationException { | |
| if (existingAmount == null || existingAmount == 0) { | |
| return; | |
| } | |
| checkLimitAndPersistReservations(account, domainId, resourceType, resourceId, existingLimitTags, -1 * existingAmount); | |
| } | |
| public CheckedReservation(Account account, ResourceType resourceType, Long amount, ReservationDao reservationDao, | |
| ResourceLimitService resourceLimitService) throws ResourceAllocationException { | |
| this(account, resourceType, null, amount, reservationDao, resourceLimitService); | |
| } | |
| @NotNull | |
| private void setGlobalLock() { | |
| String lockName = String.format("CheckedReservation-%s/%d", this.domainId, resourceType.getOrdinal()); | |
| setQuotaLimitLock(GlobalLock.getInternLock(lockName)); | |
| } | |
| protected void setQuotaLimitLock(GlobalLock quotaLimitLock) { | |
| this.quotaLimitLock = quotaLimitLock; | |
| } | |
| @Override | |
| public void close() { | |
| removeAllReservations(); | |
| } | |
| public Account getAccount() { | |
| return account; | |
| } | |
| public String getResourceLimitTagsAsString() { | |
| return CollectionUtils.isNotEmpty(reservationTags) ? StringUtils.join(reservationTags) : null; | |
| } | |
| public Long getReservedAmount() { | |
| return reservationAmount; | |
| } | |
| public List<ResourceReservation> getReservations() { | |
| return reservations; | |
| } | |
| public List<Long> getIds() { | |
| if (CollectionUtils.isEmpty(reservations)) { | |
| return new ArrayList<>(); | |
| } | |
| return reservations.stream().map(ResourceReservation::getId).collect(Collectors.toList()); | |
| } | |
| } |