bug5787: interims backup commit, starting to extract rule inserting logic

This commit is contained in:
Axel Uhl
2023-05-04 18:14:21 +02:00
parent 5999d2028e
commit 2d00697117
11 changed files with 246 additions and 20 deletions
@@ -2,21 +2,21 @@
<feature
id="com.amazon.aws.aws-java-api"
label="AWS API"
version="2.18.8"
version="2.20.59"
provider-name="Amazon">
<plugin
id="com.amazon.aws.aws-java-api"
download-size="0"
install-size="0"
version="2.18.8"
version="2.20.59"
unpack="false"/>
<plugin
id="com.amazon.aws.aws-java-api.source"
download-size="0"
install-size="0"
version="2.18.8"
version="2.20.59"
unpack="false"/>
</feature>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<site>
<feature url="features/aws-sdk/com.amazon.aws.aws-java-api_2.18.8.jar" id="com.amazon.aws.aws-java-api" version="2.18.8">
<feature url="features/aws-sdk/com.amazon.aws.aws-java-api_2.20.59.jar" id="com.amazon.aws.aws-java-api" version="2.20.59">
<category name="aws-java-api"/>
</feature>
<category-def name="com.amazon.aws.aws-java-api" label="aws-java-api"/>
@@ -5,7 +5,7 @@ repositories {
}
dependencies {
implementation platform('software.amazon.awssdk:bom:2.18.8')
implementation platform('software.amazon.awssdk:bom:2.20.59')
implementation 'software.amazon.awssdk:s3'
implementation 'software.amazon.awssdk:lambda'
implementation 'software.amazon.awssdk:ec2'
+1 -1
View File
@@ -16,7 +16,7 @@
</properties>
<groupId>com.amazon.aws</groupId>
<artifactId>com.amazon.aws.aws-java-api</artifactId>
<version>2.18.8</version>
<version>2.20.59</version>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde version="3.8"?>
<target name="Race Analysis Target" sequenceNumber="218">
<target name="Race Analysis Target" sequenceNumber="219">
<locations>
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
<unit id="javax.ws.rs" version="1.1.1.v20130318-1750"/>
@@ -147,7 +147,7 @@
<repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20210223232630/repository"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
<unit id="com.amazon.aws.aws-java-api.feature.group" version="2.18.8"/>
<unit id="com.amazon.aws.aws-java-api.feature.group" version="2.20.59"/>
<repository location="https://p2.sapsailing.com/p2/aws-sdk/"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
+2 -2
View File
@@ -742,14 +742,14 @@
id="com.amazon.aws.aws-java-api"
download-size="0"
install-size="0"
version="2.18.8"
version="2.20.59"
unpack="false"/>
<plugin
id="com.amazon.aws.aws-java-api.source"
download-size="0"
install-size="0"
version="2.18.8"
version="2.20.59"
unpack="false"/>
<plugin
@@ -53,6 +53,8 @@ public interface ApplicationLoadBalancer<ShardingKey> extends Named {
*/
public static final int MAX_PRIORITY = 50000;
AwsLandscape<ShardingKey> getLandscape();
/**
* The DNS name of this load balancer; can be used, e.g., to set a CNAME DNS record pointing
* to this load balancer.
@@ -106,10 +108,17 @@ public interface ApplicationLoadBalancer<ShardingKey> extends Named {
* original order to the resulting sequence. Otherwise, the existing rules are "compressed" by re-numbering their
* priorities to make space for the new rules at the end of the list.
*
* @param insertBefore
* if empty, the new {@code rules} can be inserted anywhere in the list of rules; if a {@link Rule} is
* specified and that rule is part of this ALB's {@link #getRules() rules},
* {@link #shiftRulesToMakeSpaceAt(int) make space} at that point and insert the new {@code rules} there.
* If the rule specified cannot be found in this ALB's {@link #getRules()}, an {@link IllegalArgumentException}
* is thrown. The rules are compared by their {@link Rule#ruleArn() ARN}, not by their Java object identity.
*
* @return copies of the original rules, with unused {@link Rule#priority() priorities} assigned, as passed already
* to {@link #addRules(Rule...)}.
*/
Iterable<Rule> addRulesAssigningUnusedPriorities(boolean forceContiguous, Rule... rules);
Iterable<Rule> addRulesAssigningUnusedPriorities(boolean forceContiguous, Optional<Rule> insertBefore, Rule... rules);
/**
* For inserting e.g. Shard at a specific priority, we must ensure that the priority is unique in the ruleset. This
@@ -8,11 +8,11 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
@@ -52,6 +52,11 @@ implements ApplicationLoadBalancer<ShardingKey> {
this.loadBalancer = loadBalancer;
this.landscape = landscape;
}
@Override
public AwsLandscape<ShardingKey> getLandscape() {
return landscape;
}
@Override
public String getName() {
@@ -202,8 +207,7 @@ implements ApplicationLoadBalancer<ShardingKey> {
}
@Override
public Iterable<Rule> addRulesAssigningUnusedPriorities(boolean forceContiguous, Rule... rules) {
// FIXME: shouldn't we check for ApplicationLoadBalancer.MAX_RULES_PER_LOADBALANCER before adding the rules?
public Iterable<Rule> addRulesAssigningUnusedPriorities(boolean forceContiguous, Optional<Rule> insertBefore, Rule... rules) {
// TODO bug5787: allow caller to specify an insertion point; either as an index or as a rule before which to insert
final Iterable<Rule> existingRules = getRules();
if (Util.size(existingRules)-1 + rules.length > MAX_PRIORITY) { // -1 due to the default rule being part of existingRules
@@ -211,6 +215,11 @@ implements ApplicationLoadBalancer<ShardingKey> {
(Util.size(existingRules)-1)+" of them and together they would exceed the maximum of "+MAX_PRIORITY+" by "+
(Util.size(existingRules)-1 + rules.length - MAX_PRIORITY));
}
if (Util.size(existingRules) + rules.length > MAX_RULES_PER_LOADBALANCER) {
throw new IllegalArgumentException("The " + rules.length + " new rules would make the ALB " + getName()
+ " exceed its maximum number of rules (" + MAX_PRIORITY + ") by "
+ (Util.size(existingRules) + rules.length - MAX_RULES_PER_LOADBALANCER));
}
final List<Rule> result = new ArrayList<>(rules.length);
final List<Rule> sortedExistingNonDefaultRules = new ArrayList<>(Util.size(existingRules)-1);
Util.addAll(Util.filter(existingRules, r->!r.isDefault()), sortedExistingNonDefaultRules);
@@ -227,7 +236,11 @@ implements ApplicationLoadBalancer<ShardingKey> {
while (rulesIndex < rules.length) {
// find next available slot
int nextPriority = MAX_PRIORITY+1; // if no further rule exists, the usable gap ends after MAX_PRIORITY
while (existingRulesIter.hasNext() && (nextPriority=Integer.valueOf(existingRulesIter.next().priority())) <= previousPriority+stepwidth) {
final Rule[] nextRule = new Rule[1]; // using an array to make it final so we can access it inside the mapping function below
boolean stillLookingForRuleToInsertBefore = insertBefore.isPresent();
while (existingRulesIter.hasNext() &&
((nextPriority=Integer.valueOf((nextRule[0]=existingRulesIter.next()).priority())) <= previousPriority+stepwidth) ||
(stillLookingForRuleToInsertBefore && (stillLookingForRuleToInsertBefore=insertBefore.map(rule->!rule.ruleArn().equals(nextRule[0].ruleArn())).orElse(false)))) {
// not enough space for stepwidth many rules; keep on searching
previousPriority = nextPriority;
if (!existingRulesIter.hasNext()) {
@@ -330,9 +343,10 @@ implements ApplicationLoadBalancer<ShardingKey> {
* @return the new default redirect rule that was inserted into this load balancer's HTTPS listener's rule set
*/
private Rule insertAndReturnDefaultRedirectRule(String hostname, String pathWithLeadingSlash, Optional<String> query) {
// FIXME bug 5787: this rule would then typically end up at the end of the rule set, being superseded by all other rules for the replica set; use shiftRulesToMakeSpaceAt with the first rule priority of the replica set identified by hostname
final Rule defaultRedirectRule = createDefaultRedirectRule(hostname, pathWithLeadingSlash, query);
addRulesAssigningUnusedPriorities(/* forceContiguous */ false, defaultRedirectRule);
addRulesAssigningUnusedPriorities(/* forceContiguous */ false,
Util.stream(getRules()).filter(rule->rule.conditions().stream().anyMatch(condition->condition.field().equals("host-header") && condition.hostHeaderConfig().values().contains(hostname))).findFirst(),
defaultRedirectRule);
return defaultRedirectRule;
}
@@ -0,0 +1,202 @@
package com.sap.sse.landscape.aws.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import com.sap.sse.common.Named;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.landscape.aws.ApplicationLoadBalancer;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Rule;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.RulePriorityPair;
/**
* Manages a set of rules from an Application Load Balancer. This includes adding a set of rules either contiguously or
* optionally non-contiguously to an existing set of rules that are ordered by their priorities. There are limits on the
* maximum number of rules and on the maximum priority that can be used. Trying to add too many rules so that one of
* these limits would have to be violated to accommodate will throw an exception.
* <p>
*
* For purposes of testability, the class is designed such that the actual {@link Rule} and {@link RulePriorityPair}
* classes are abstracted by an interface. Default adapters are available for the actual ALB-related classes, but tests
* may provide their own mocks instead so that it is not required to test this class against a real AWS environment.
*
* @author Axel Uhl (d043530)
*
*/
public class LoadBalancerRuleInserter<ShardingKey, RA extends com.sap.sse.landscape.aws.impl.LoadBalancerRuleInserter.RuleAdapter> {
private final LoadBalancerAdapter<RA> loadBalancerAdapter;
private final int maxPriority;
private final int maxRulesParLoadBalancer;
public static interface RuleAdapter {
boolean isDefault();
String priority();
String ruleArn();
RuleAdapter copyWithNewPriority(int priorityToUseForRuleCopy);
}
public static interface LoadBalancerAdapter<RA extends RuleAdapter> extends Named {
Iterable<RA> getRules();
void updateLoadBalancerListenerRulePriorities(List<Pair<Integer, RA>> newPrioritiesForExistingRules);
void addRules(List<RA> result);
}
private static class ALBRuleAdapter implements RuleAdapter {
private final Rule rule;
private ALBRuleAdapter(Rule rule) {
this.rule = rule;
}
@Override
public boolean isDefault() {
return rule.isDefault();
}
@Override
public String priority() {
return rule.priority();
}
@Override
public String ruleArn() {
return rule.ruleArn();
}
@Override
public RuleAdapter copyWithNewPriority(int priorityToUseForRuleCopy) {
return new ALBRuleAdapter(rule.copy(b->b.priority(""+priorityToUseForRuleCopy).build()));
}
Rule getRule() {
return rule;
}
}
private static class ALBAdapter<ShardingKey> implements LoadBalancerAdapter<ALBRuleAdapter> {
private static final long serialVersionUID = -4337640786328697695L;
private final ApplicationLoadBalancer<ShardingKey> alb;
private ALBAdapter(ApplicationLoadBalancer<ShardingKey> alb) {
super();
this.alb = alb;
}
@Override
public String getName() {
return alb.getName();
}
@Override
public Iterable<ALBRuleAdapter> getRules() {
return Util.map(alb.getRules(), r->createRuleAdapter(r));
}
@Override
public void updateLoadBalancerListenerRulePriorities(
List<Pair<Integer, ALBRuleAdapter>> newPrioritiesForExistingRules) {
alb.getLandscape().updateLoadBalancerListenerRulePriorities(alb.getRegion(),
Util.map(newPrioritiesForExistingRules, p->RulePriorityPair.builder().priority(p.getA()).ruleArn(p.getB().ruleArn()).build()));
}
@Override
public void addRules(List<ALBRuleAdapter> rules) {
alb.addRules(Util.toArray(Util.map(rules, ra->ra.getRule()), new Rule[0]));
}
}
public LoadBalancerRuleInserter(LoadBalancerAdapter<RA> loadBalancerAdapter, int maxPriority, int maxRulesParLoadBalancer) {
super();
this.loadBalancerAdapter = loadBalancerAdapter;
this.maxPriority = maxPriority;
this.maxRulesParLoadBalancer = maxRulesParLoadBalancer;
}
static ALBRuleAdapter createRuleAdapter(Rule rule) {
return new ALBRuleAdapter(rule);
}
static <ShardingKey> ALBAdapter<ShardingKey> createLoadBalancerAdapter(ApplicationLoadBalancer<ShardingKey> alb) {
return new ALBAdapter<ShardingKey>(alb);
}
public List<RuleAdapter> addRulesAssigningUnusedPriorities(boolean forceContiguous, Optional<RA> insertBefore, RA... rules) {
final Iterable<RA> existingRules = loadBalancerAdapter.getRules();
if (Util.size(existingRules)-1 + rules.length > maxPriority) { // -1 due to the default rule being part of existingRules
throw new IllegalArgumentException("The "+rules.length+" new rules won't find enough unused priority numbers because there are already "+
(Util.size(existingRules)-1)+" of them and together they would exceed the maximum of "+maxPriority+" by "+
(Util.size(existingRules)-1 + rules.length - maxPriority));
}
if (Util.size(existingRules) + rules.length > maxRulesParLoadBalancer) {
throw new IllegalArgumentException("The " + rules.length + " new rules would make the ALB " + loadBalancerAdapter.getName()
+ " exceed its maximum number of rules (" + maxPriority + ") by "
+ (Util.size(existingRules) + rules.length - maxRulesParLoadBalancer));
}
final List<RA> result = new ArrayList<>(rules.length);
final List<RA> sortedExistingNonDefaultRules = new ArrayList<>(Util.size(existingRules)-1);
Util.addAll(Util.filter(existingRules, r->!r.isDefault()), sortedExistingNonDefaultRules);
Collections.sort(sortedExistingNonDefaultRules, (r1, r2)->Integer.valueOf(r1.priority()).compareTo(Integer.valueOf(r2.priority())));
final int stepwidth;
if (forceContiguous) {
stepwidth = rules.length;
} else {
stepwidth = 1;
}
int rulesIndex = 0;
int previousPriority = 0;
final Iterator<RA> existingRulesIter = sortedExistingNonDefaultRules.iterator();
while (rulesIndex < rules.length) {
// find next available slot
int nextPriority = maxPriority+1; // if no further rule exists, the usable gap ends after MAX_PRIORITY
final RA[] nextRule = new RA[1]; // using an array to make it final so we can access it inside the mapping function below
boolean stillLookingForRuleToInsertBefore = insertBefore.isPresent();
while (existingRulesIter.hasNext() &&
((nextPriority=Integer.valueOf((nextRule[0]=existingRulesIter.next()).priority())) <= previousPriority+stepwidth) ||
(stillLookingForRuleToInsertBefore && (stillLookingForRuleToInsertBefore=insertBefore.map(rule->!rule.ruleArn().equals(nextRule[0].ruleArn())).orElse(false)))) {
// not enough space for stepwidth many rules; keep on searching
previousPriority = nextPriority;
if (!existingRulesIter.hasNext()) {
nextPriority = maxPriority+1;
}
}
if (previousPriority+stepwidth > maxPriority) {
if (forceContiguous) {
previousPriority = squeezeExistingRulesAndReturnLastUsedPriority(sortedExistingNonDefaultRules);
nextPriority = maxPriority+1;
// we previously checked already that there is enough room for the new set of rules
assert previousPriority + rules.length <= maxPriority;
} else {
throw new IllegalStateException(
"The " + rules.length + " new rules don't fit into the existing rule set of load balancer "
+ loadBalancerAdapter.getName() + " without exceeding the maximum priority of " + maxPriority);
}
}
while (rulesIndex < rules.length && ++previousPriority < nextPriority) {
final int priorityToUseForNextRule = previousPriority;
result.add(rules[rulesIndex++].copyWithNewPriority(priorityToUseForNextRule));
}
}
loadBalancerAdapter.addRules(result);
return result;
}
private int squeezeExistingRulesAndReturnLastUsedPriority(final List<RA> sortedExistingNonDefaultRules) {
final List<Pair<Integer, RuleAdapter>> newPrioritiesForExistingRules = new LinkedList<>();
int priority = 0;
for (final RuleAdapter existingRule : sortedExistingNonDefaultRules) {
newPrioritiesForExistingRules.add(new Pair<>(++priority, existingRule));
}
loadBalancerAdapter.updateLoadBalancerListenerRulePriorities(newPrioritiesForExistingRules);
return priority;
}
}
@@ -225,7 +225,7 @@ implements ProcedureCreatingLoadBalancerMapping<ShardingKey> {
getLandscape().addTargetsToTargetGroup(masterTargetGroupCreated, Collections.singleton(getHost()));
getLandscape().addTargetsToTargetGroup(publicTargetGroupCreated, Collections.singleton(getHost()));
getLoadBalancerUsed().addRulesAssigningUnusedPriorities(/* forceContiguous */ true,
createRules(getLoadBalancerUsed(), getHostName(), masterTargetGroupCreated, publicTargetGroupCreated));
Optional.empty(), createRules(getLoadBalancerUsed(), getHostName(), masterTargetGroupCreated, publicTargetGroupCreated));
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -7,6 +7,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
@@ -376,7 +377,7 @@ implements ProcedureCreatingLoadBalancerMapping<ShardingKey> {
Map<TargetGroup<ShardingKey>, Iterable<ShardingKey>> shardingKeysPerTargetGroup) throws Exception {
targetAlb
.addRulesAssigningUnusedPriorities(/* forceContiguous */ true,
createRules(targetAlb, replicaSet.getHostname(),
Optional.empty(), createRules(targetAlb, replicaSet.getHostname(),
targetGroupsToTempTargetgroups.get(replicaSetToMove.getMasterTargetGroup()),
targetGroupsToTempTargetgroups.get(replicaSetToMove.getPublicTargetGroup())))
.forEach(t -> tempRules.add(t));