added implementation for using conditions as the base of the

construction of rules and not sharding keys.
This commit is contained in:
Niklas Opiela committed 2023-08-14 13:55:37 +02:00
1 parent 25a6d0cea6
commit ef730bf83c
3 files changed
+129 -67

No files matched your search

@@ -0,0 +1,55 @@
package com.sap.sse.landscape.aws.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import com.sap.sse.common.Builder;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.RuleCondition;
public class ShardingRuleConditionBuilder<ShardingKey> implements Builder<ShardingRuleConditionBuilder<ShardingKey>,Collection<RuleCondition>>{
// two dimensional array with keys [condition][beforeKey(0)/Afterkey(1)]
static final String[][] construction = {
{"*/leaderboard/", ""},
{"*/v?/leaderboards/",""},
{"*/v?/leaderboards/", "/*"},
{"*/v?/regattas/", ""},
{"*/v?/regattas/","/*"}
};
public static int numberOfShardConditionsPerShard() {
return construction.length;
}
private ShardingKey shardingKey;
public ShardingRuleConditionBuilder<ShardingKey> ShardingKey(ShardingKey key) {
shardingKey = key;
return this;
}
@Override
public Collection<RuleCondition> build() throws Exception {
final Collection<RuleCondition> ruleConditions = new ArrayList<>();
final Collection<String> paths = getPathsForShardingKey(shardingKey);
ruleConditions.add(
RuleCondition.builder().field("path-pattern").pathPatternConfig(hhcb -> hhcb.values(paths)).build());
return ruleConditions;
}
protected Collection<RuleCondition> getPathConditionsForOneShardingKey(ShardingKey shardingKey) throws InterruptedException, ExecutionException {
final Collection<RuleCondition> ruleConditions = new ArrayList<>();
final Collection<String> paths = getPathsForShardingKey(shardingKey);
ruleConditions.add(
RuleCondition.builder().field("path-pattern").pathPatternConfig(hhcb -> hhcb.values(paths)).build());
return ruleConditions;
}
// returns every required path condition
Collection<String> getPathsForShardingKey(ShardingKey shardingKey) {
ArrayList<String> c = new ArrayList<>();
for (String[] i : construction) {
c.add(i[0] + shardingKey + i[1]);
}
return c;
}
}
@@ -2,11 +2,7 @@ package com.sap.sse.landscape.aws.orchestration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import com.sap.sse.common.Util;
@@ -15,6 +11,7 @@ import com.sap.sse.landscape.application.ApplicationProcessMetrics;
import com.sap.sse.landscape.aws.ApplicationLoadBalancer;
import com.sap.sse.landscape.aws.AwsShard;
import com.sap.sse.landscape.aws.TargetGroup;
import com.sap.sse.landscape.aws.impl.ShardingRuleConditionBuilder;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Rule;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.RuleCondition;
@@ -65,41 +62,41 @@ public class AddShardingKeyToShard<ShardingKey, MetricsT extends ApplicationProc
throw new Exception("Shard "+shardName+" not found in replica set "+replicaSet.getName());
}
logger.info("Appending " + Util.joinStrings(", ", shardingKeys) + " to " + shardName);
// list for manipulation -> elements are allowed to be removed!!
final List<ShardingKey> mutableShardingKeys = new LinkedList<>();
mutableShardingKeys.addAll(shardingKeys);
final TargetGroup<ShardingKey> targetgroup = shard.getTargetGroup();
final ApplicationLoadBalancer<ShardingKey> loadBalancer = shard.getLoadBalancer();
final Collection<TargetGroup<ShardingKey>> t = new ArrayList<>();
t.add(targetgroup);
// final Collection<TargetGroup<ShardingKey>> t = new ArrayList<>();
// t.add(targetgroup);
ArrayList<RuleCondition> ruleConditionToBeInserted = new ArrayList<>();
for (ShardingKey k : shardingKeys) {
ruleConditionToBeInserted.addAll((new ShardingRuleConditionBuilder<>().ShardingKey(k).build()));
}
// check if there is a rule with space left for one or more additional conditions:
for (Rule r : shard.getRules()) {
boolean updateRule = false;
final ArrayList<ShardingKey> shardingKeys = new ArrayList<>();
boolean ruleIsEmpty = true;
ArrayList<RuleCondition> conditionsForModThisRule = new ArrayList<>();
for (RuleCondition con : r.conditions()) {
// if we find a
if (con.pathPatternConfig() != null) {
// eliminate PATH_UNUSED_BY_ANY_APPLICATION in case this proxy key was found;
// it usually indicates an empty shard; when now adding one or more conditions
// it can be replaced.
Util.addAll(
Util.filter(
Util.map(con.values(), this::getShardingKeyFromPathCondition),
shardingKey->!shardingKey.equals(SHARDING_KEY_UNUSED_BY_ANY_APPLICATION)),
shardingKeys);
if (con.pathPatternConfig() != null && Util.size(Util.filter(con.values(), t -> getShardingKeyFromPathCondition(t) != SHARDING_KEY_UNUSED_BY_ANY_APPLICATION)) != 0) {
// for rebuilding the conditions later, we need to add already existing conditions.
conditionsForModThisRule.add(con);
// if there are paths which are not SHARDING_KEY_UNUSED_BY_ANY_APPLICATION, than the rule is not empty.
ruleIsEmpty = false;
}
}
if (shardingKeys.isEmpty()) {
if (ruleIsEmpty) {
// the rule probably only has PATH_UNUSED_BY_ANY_APPLICATION and was a proxy rule, probably at the end of the list; remove
loadBalancer.deleteRules(r);
} else { // update only non-empty rule because we assume it won't be at the end of the list
while (shardingKeys.size() < ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE - NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE
&& !mutableShardingKeys.isEmpty()) {
shardingKeys.add(mutableShardingKeys.get(0));
mutableShardingKeys.remove(0);
while (conditionsForModThisRule.size() < ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE - NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE
&& !ruleConditionToBeInserted.isEmpty()) {
conditionsForModThisRule.add(ruleConditionToBeInserted.get(0));
ruleConditionToBeInserted.remove(0);
updateRule = true;
}
final Collection<RuleCondition> ruleConditions = getShardingRuleConditions(loadBalancer, shardingKeys);
final Collection<RuleCondition> ruleConditions = getFullConditionSetFromShardingConditions(loadBalancer, conditionsForModThisRule);
// construct a rule only for transporting the conditions; no forwarding target is required for modifyRuleConditions
Rule proxyRuleWithNewConditions = Rule.builder().ruleArn(r.ruleArn()).conditions(ruleConditions).build();
if (updateRule) {
@@ -107,19 +104,17 @@ public class AddShardingKeyToShard<ShardingKey, MetricsT extends ApplicationProc
}
}
}
if (!mutableShardingKeys.isEmpty()) {
if (!ruleConditionToBeInserted.isEmpty()) {
// check number of rules
final Set<ShardingKey> keysCopy = new HashSet<>();
keysCopy.addAll(shardingKeys);
if (Util.size(loadBalancer.getRules()) + numberOfRequiredRules(Util.size(shardingKeys))
< ApplicationLoadBalancer.MAX_RULES_PER_LOADBALANCER) {
// enough rules
addShardingRules(loadBalancer, keysCopy, targetgroup);
addNewRulesFromPathConditions(ruleConditionToBeInserted, loadBalancer, targetgroup);
} else {
// not enough rules
final ApplicationLoadBalancer<ShardingKey> alb = getFreeLoadBalancerAndMoveReplicaSet();
// set new rules
addShardingRules(alb, keysCopy, targetgroup);
addNewRulesFromPathConditions(ruleConditionToBeInserted, alb, targetgroup);
}
}
}
@@ -28,6 +28,8 @@ import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.AwsShard;
import com.sap.sse.landscape.aws.TargetGroup;
import com.sap.sse.landscape.aws.impl.LoadBalancerRuleInserter;
import com.sap.sse.landscape.aws.impl.LoadBalancerRuleInserter.ALBRuleAdapter;
import com.sap.sse.landscape.aws.impl.ShardingRuleConditionBuilder;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Action;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.ActionTypeEnum;
@@ -176,53 +178,50 @@ implements ProcedureCreatingLoadBalancerMapping<ShardingKey> {
final Iterable<TargetGroup<ShardingKey>> targetGroups = getLandscape().getTargetGroups(region);
return Util.isEmpty(Util.filter(targetGroups, t -> t.getName().equals(name)));
}
/**
* Produces conditions for a sharding load balancer rule based on the {@link #replicaSet}'s
* {@link AwsApplicationReplicaSet#getHostname() hostname}, a header-field condition that requires the request to be
* tagged for a replica, plus a path-pattern condition with the sharding keys as patterns.
*
* @param shardingKeys
* their number must not exceed {@link ApplicationLoadBalancer#MAX_CONDITIONS_PER_RULE} -
* {@link #NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE}; pass sharding keys (as the name suggests),
* not full paths; the paths for the {@code path-pattern} condition will be constructed from the sharding
* keys by this method. See also {@link #getPathConditionForShardingKey(String, String)}.
*/
protected Collection<RuleCondition> getShardingRuleConditions(ApplicationLoadBalancer<ShardingKey> loadBalancer,
Collection<ShardingKey> shardingKeys) throws InterruptedException, ExecutionException {
if (shardingKeys.size() > ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE - NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE) {
throw new IllegalArgumentException("too many sharding keys for the conditions of a single load balancer rule: "+shardingKeys+
"; a maximum of "+(ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE - NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE)+" is allowed");
protected Collection<RuleCondition> getFullConditionSetFromShardingConditions(ApplicationLoadBalancer<ShardingKey> loadBalancer,
Collection<RuleCondition> shardRuleConditions) throws InterruptedException, ExecutionException {
if (shardRuleConditions.size() > ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE - NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE) {
throw new IllegalArgumentException("Too many shardRuleConditions for a Rule. One ShardRule can contain a maximum of " + (ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE - NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE) + " Path conditions because we need" +
NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE + " for matching these rules to a replicaset!");
}
final Collection<RuleCondition> ruleConditions = new ArrayList<>();
final Collection<String> paths = Util.mapToArrayList(shardingKeys, shardingKey->getPathConditionForShardingKey(shardingKey, pathPrefixForShardingKey));
ruleConditions.add(loadBalancer.createHostHeaderRuleCondition(replicaSet.getHostname()));
// add extra rules
ruleConditions.addAll(shardRuleConditions);
ruleConditions.add(RuleCondition.builder().field("http-header")
.httpHeaderConfig(hhcb -> hhcb.httpHeaderName(HttpRequestHeaderConstants.HEADER_KEY_FORWARD_TO)
.values(HttpRequestHeaderConstants.HEADER_FORWARD_TO_REPLICA.getB()))
.build());
ruleConditions.add(
RuleCondition.builder().field("path-pattern").pathPatternConfig(hhcb -> hhcb.values(paths)).build());
return ruleConditions;
ruleConditions.add(loadBalancer.createHostHeaderRuleCondition(replicaSet.getHostname()));
return ruleConditions;
}
protected Iterable<Rule> addShardingRules(ApplicationLoadBalancer<ShardingKey> alb, Iterable<ShardingKey> shardingKeys,
TargetGroup<ShardingKey> targetGroup) throws Exception {
// change ALB rules to new ones
final Collection<Rule> rules = new ArrayList<Rule>();
final Set<ShardingKey> shardingKeyForConsumption = new HashSet<>();
Util.addAll(shardingKeys, shardingKeyForConsumption);
/**
* This function constructs a number of new rules for given {@code ruleConditions}.
* They are constructed by creating rules and filling them with those conditions and the needed sharding rules.
* And the forward to {@code targetgroup} gets also added.
* @param ruleConditions are all condition, e.g. "paths", which are planned to be inserted.
* @param alb Automatic loadbalancer where these rules are planned to be added
* @param targetGroup targetgroup where the forward of the rule points to
* @return all created rules which still need to be fed into the loadbalancer
* @throws Exception
*/
protected Iterable<Rule> addNewRulesFromPathConditions(Iterable<RuleCondition> ruleConditions, ApplicationLoadBalancer<ShardingKey> alb, TargetGroup<ShardingKey> targetGroup) throws Exception{
final ArrayList<Rule> rules = new ArrayList<>();
final int ruleIdx = alb.getFirstShardingPriority(replicaSet.getHostname());
while (!shardingKeyForConsumption.isEmpty()) {
LoadBalancerRuleInserter.create(alb, ApplicationLoadBalancer.MAX_PRIORITY, ApplicationLoadBalancer.MAX_RULES_PER_LOADBALANCER).shiftRulesToMakeSpaceAt(ruleIdx, 1);
final Set<ShardingKey> shardingKeysForNextRule = new HashSet<>();
for (final Iterator<ShardingKey> i=shardingKeyForConsumption.iterator();
shardingKeysForNextRule.size() < ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE-NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE && i.hasNext(); ) {
shardingKeysForNextRule.add(i.next());
final Set<RuleCondition> ruleConditionsForConsumption = new HashSet<>();
Util.addAll(ruleConditions, ruleConditionsForConsumption);
while (!ruleConditionsForConsumption.isEmpty()) {
LoadBalancerRuleInserter<ShardingKey, ALBRuleAdapter> rulesInserter = LoadBalancerRuleInserter.create(alb, ApplicationLoadBalancer.MAX_PRIORITY, ApplicationLoadBalancer.MAX_RULES_PER_LOADBALANCER);
rulesInserter.shiftRulesToMakeSpaceAt(ruleIdx, 1);
final Collection<RuleCondition> conditionsForNextRule = new ArrayList<>();
for (final Iterator<RuleCondition> i=ruleConditionsForConsumption.iterator();
conditionsForNextRule.size() < ApplicationLoadBalancer.MAX_CONDITIONS_PER_RULE-NUMBER_OF_STANDARD_CONDITIONS_FOR_SHARDING_RULE && i.hasNext(); ) {
conditionsForNextRule.add(i.next());
i.remove();
}
final Collection<RuleCondition> conditions = getShardingRuleConditions(alb, shardingKeysForNextRule);
rules.add(Rule.builder().priority("" + ruleIdx).conditions(conditions)
//Build rule with new conditions
rules.add(Rule.builder().priority("" + ruleIdx).conditions(conditionsForNextRule)
.actions(Action.builder()
.forwardConfig(ForwardActionConfig.builder()
.targetGroups(TargetGroupTuple.builder()
@@ -233,6 +232,17 @@ implements ProcedureCreatingLoadBalancerMapping<ShardingKey> {
}
return alb.addRules(Util.toArray(rules, new Rule[0]));
}
protected Iterable<Rule> addShardingRules(ApplicationLoadBalancer<ShardingKey> alb, Iterable<ShardingKey> shardingKeys,
TargetGroup<ShardingKey> targetGroup) throws Exception {
// change ALB rules to new ones
// construct all conditions for all shardingkeys because one sharding key needs more than one condition
final ArrayList<RuleCondition> conditions = new ArrayList<>();
for (ShardingKey k : shardingKeys) {
conditions.addAll((new ShardingRuleConditionBuilder<>().ShardingKey(k).build()));
}
return addNewRulesFromPathConditions(conditions, alb, targetGroup);
}
/**
* Search through all load balancers and returns the first load balancer with enough rules left for
@@ -447,6 +457,7 @@ implements ProcedureCreatingLoadBalancerMapping<ShardingKey> {
/**
* Path conditions are constructed by pre-pending a "*" to the sharding key.
*/
//TODO delete -> changed so ShardingRulesBuilder
public static <ShardingKey> String getPathConditionForShardingKey(ShardingKey shardingKey, String pathPrefixForShardingKey) {
return pathPrefixForShardingKey+shardingKey.toString();
}
@@ -468,6 +479,7 @@ implements ProcedureCreatingLoadBalancerMapping<ShardingKey> {
}
protected ShardingKey getShardingKeyFromPathCondition(String path) {
// TODO change because we've got more than one condition per Sharding key
return getShardingKeyFromPathCondition(path, pathPrefixForShardingKey);
}
}