-
Notifications
You must be signed in to change notification settings - Fork 226
Target Group Quota introduction #3337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ | |
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import java.util.SortedSet; | ||
| import java.util.TreeSet; | ||
| import java.util.function.BiFunction; | ||
| import java.util.function.Consumer; | ||
| import java.util.function.Function; | ||
|
|
@@ -34,6 +36,7 @@ | |
| import jakarta.validation.constraints.NotEmpty; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.eclipse.hawkbit.context.AccessContext; | ||
| import org.eclipse.hawkbit.ql.jpa.QLSupport; | ||
| import org.eclipse.hawkbit.repository.QuotaManagement; | ||
| import org.eclipse.hawkbit.repository.TargetManagement; | ||
|
|
@@ -102,6 +105,50 @@ protected JpaTargetManagement( | |
| this.targetTagRepository = targetTagRepository; | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) | ||
| public JpaTarget create(final TargetManagement.Create create) { | ||
| assertTargetGroupQuota(Collections.singletonList(create.getGroup())); | ||
| return super.create(create); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) | ||
| public List<JpaTarget> create(final Collection<TargetManagement.Create> create) { | ||
| assertTargetGroupQuota(create.stream().map(TargetManagement.Create::getGroup).toList()); | ||
| return super.create(create); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) | ||
| public JpaTarget update(final TargetManagement.Update update) { | ||
| try { | ||
| assertTargetGroupQuota(Collections.singletonList(update.getGroup())); | ||
| } catch (final Exception ex) { | ||
| // target existence check in order to throw EntityNotFound instead of AssignmentQuotaException if both applicable | ||
| getValid(update.getId()); | ||
| throw ex; | ||
| } | ||
| return super.update(update); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) | ||
| public Map<Long, JpaTarget> update(final Collection<TargetManagement.Update> update) { | ||
| try { | ||
| assertTargetGroupQuota(update.stream().map(TargetManagement.Update::getGroup).toList()); | ||
| } catch (final Exception ex) { | ||
| // target existence check in order to throw EntityNotFound instead of AssignmentQuotaException if both applicable | ||
| get(update.stream().map(TargetManagement.Update::getId).toList()); | ||
| throw ex; | ||
| } | ||
| return super.update(update); | ||
| } | ||
|
|
||
| @Override | ||
| public Map<String, String> getControllerAttributes(final String controllerId) { | ||
| return getMap(controllerId, JpaTarget_.controllerAttributes); | ||
|
|
@@ -339,6 +386,10 @@ public Target unassignType(final String controllerId) { | |
| @Transactional | ||
| @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) | ||
| public void assignTargetGroupWithRsql(String group, String rsql) { | ||
| // Quota check | ||
| if (group != null) { | ||
| assertTargetGroupQuota(Collections.singletonList(group)); | ||
| } | ||
|
|
||
| // Switch back to UpdateAllQuery if switching back to hibernate. (EclipseLink does not work well with UpdateAllQuery) | ||
| // EclipseLink: using subquery approach — applying predicate directly to the UPDATE root | ||
|
|
@@ -401,6 +452,11 @@ private void assignTargetGroupOnChunks(final String group, final String rsql) { | |
| @Transactional | ||
| @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) | ||
| public void assignTargetsWithGroup(String group, List<String> controllerIds) { | ||
| // Quota check | ||
| if (group != null) { | ||
| assertTargetGroupQuota(Collections.singletonList(group)); | ||
| } | ||
|
|
||
| final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); | ||
| CriteriaUpdate<JpaTarget> criteriaQuery = cb.createCriteriaUpdate(JpaTarget.class); | ||
| Root<JpaTarget> root = criteriaQuery.from(JpaTarget.class); | ||
|
|
@@ -552,4 +608,36 @@ private void throwEntityNotFoundExceptionIfTagDoesNotExist(final Long tagId) { | |
| throw new EntityNotFoundException(TargetTag.class, tagId); | ||
| } | ||
| } | ||
|
|
||
| private void assertTargetGroupQuota(final Collection<String> requested) { | ||
| final long limit = quotaManagement.getMaxTargetGroups(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if wanted is empty - no need to getMaxTargetGroups, which at some point could become db managed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point, will switch the order |
||
| if (limit <= 0) { | ||
| return; | ||
| } | ||
|
|
||
| final SortedSet<String> wanted = requested.stream() | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. db could be case sensitive?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, agreed - I'll let the db collation do the job without specifying in java for it |
||
|
|
||
| if (wanted.isEmpty()) { | ||
| return; // no group(s), skip findDistinctGroups db call | ||
| } | ||
|
|
||
| // one group, already present -> allowed | ||
| if (wanted.size() == 1 && jpaRepository.existsByGroup(wanted.first())) { | ||
| return; | ||
| } | ||
| final List<String> existing = jpaRepository.findDistinctGroups(AccessContext.tenant()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it could be the case that updating a group other is removed, e.g.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't quite get the second one - don't we actually currently do that ? We don't allow not existing groups (currently) when the limit is reached . |
||
| final Set<String> existingCi = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); | ||
| existingCi.addAll(existing); | ||
|
|
||
| wanted.removeAll(existingCi); | ||
| if (wanted.isEmpty()) { | ||
| return; // no growth -> allowed | ||
| } | ||
|
|
||
| QuotaHelper.assertAssignmentQuota( | ||
| AccessContext.tenant(), wanted.size(), limit, "target group", "tenant", | ||
| tenant -> existing.size()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why here we don't check for group == null, but on some places we do check>?
same on update
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually do on later point - on wanted.isEmpty() in the assertTargetGroupQuota. It is possible to directly check in the create method, but I have decided the code would become more complex to read idk ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I guess the two guards in assign methods are redundant here ... Will remove them in order to comply with all the others.