From ef730bf83cc1588d9cde64b69765c34f1f4e77a4 Mon Sep 17 00:00:00 2001 From: Niklas Opiela Date: Mon, 14 Aug 2023 13:55:37 +0200 Subject: [PATCH] added implementation for using conditions as the base of the construction of rules and not sharding keys. --- .../impl/ShardingRuleConditionBuilder.java | 55 ++++++++++++ .../orchestration/AddShardingKeyToShard.java | 55 ++++++------ .../aws/orchestration/ShardProcedure.java | 86 +++++++++++-------- 3 files changed, 129 insertions(+), 67 deletions(-) create mode 100644 java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/ShardingRuleConditionBuilder.java diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/ShardingRuleConditionBuilder.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/ShardingRuleConditionBuilder.java new file mode 100644 index 00000000000..1fc2547f74e --- /dev/null +++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/ShardingRuleConditionBuilder.java @@ -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 implements Builder,Collection>{ + + // 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 key) { + shardingKey = key; + return this; + } + + @Override + public Collection build() throws Exception { + final Collection ruleConditions = new ArrayList<>(); + final Collection paths = getPathsForShardingKey(shardingKey); + ruleConditions.add( + RuleCondition.builder().field("path-pattern").pathPatternConfig(hhcb -> hhcb.values(paths)).build()); + return ruleConditions; + } + + protected Collection getPathConditionsForOneShardingKey(ShardingKey shardingKey) throws InterruptedException, ExecutionException { + final Collection ruleConditions = new ArrayList<>(); + final Collection paths = getPathsForShardingKey(shardingKey); + ruleConditions.add( + RuleCondition.builder().field("path-pattern").pathPatternConfig(hhcb -> hhcb.values(paths)).build()); + return ruleConditions; + } + + // returns every required path condition + Collection getPathsForShardingKey(ShardingKey shardingKey) { + ArrayList c = new ArrayList<>(); + for (String[] i : construction) { + c.add(i[0] + shardingKey + i[1]); + } + return c; + } + + +} diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AddShardingKeyToShard.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AddShardingKeyToShard.java index ed10ddafd54..d8987f35e66 100644 --- a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AddShardingKeyToShard.java +++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AddShardingKeyToShard.java @@ -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 elements are allowed to be removed!! - final List mutableShardingKeys = new LinkedList<>(); - mutableShardingKeys.addAll(shardingKeys); final TargetGroup targetgroup = shard.getTargetGroup(); final ApplicationLoadBalancer loadBalancer = shard.getLoadBalancer(); - final Collection> t = new ArrayList<>(); - t.add(targetgroup); +// final Collection> t = new ArrayList<>(); +// t.add(targetgroup); + + ArrayList 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 shardingKeys = new ArrayList<>(); + boolean ruleIsEmpty = true; + ArrayList 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 ruleConditions = getShardingRuleConditions(loadBalancer, shardingKeys); + final Collection 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 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 alb = getFreeLoadBalancerAndMoveReplicaSet(); // set new rules - addShardingRules(alb, keysCopy, targetgroup); + addNewRulesFromPathConditions(ruleConditionToBeInserted, alb, targetgroup); } } } diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/ShardProcedure.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/ShardProcedure.java index 8a958305d66..8e31317d80c 100644 --- a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/ShardProcedure.java +++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/ShardProcedure.java @@ -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 { final Iterable> 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 getShardingRuleConditions(ApplicationLoadBalancer loadBalancer, - Collection 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 getFullConditionSetFromShardingConditions(ApplicationLoadBalancer loadBalancer, + Collection 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 ruleConditions = new ArrayList<>(); - final Collection 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 addShardingRules(ApplicationLoadBalancer alb, Iterable shardingKeys, - TargetGroup targetGroup) throws Exception { - // change ALB rules to new ones - final Collection rules = new ArrayList(); - final Set 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 addNewRulesFromPathConditions(Iterable ruleConditions, ApplicationLoadBalancer alb, TargetGroup targetGroup) throws Exception{ + final ArrayList 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 shardingKeysForNextRule = new HashSet<>(); - for (final Iterator 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 ruleConditionsForConsumption = new HashSet<>(); + Util.addAll(ruleConditions, ruleConditionsForConsumption); + while (!ruleConditionsForConsumption.isEmpty()) { + LoadBalancerRuleInserter rulesInserter = LoadBalancerRuleInserter.create(alb, ApplicationLoadBalancer.MAX_PRIORITY, ApplicationLoadBalancer.MAX_RULES_PER_LOADBALANCER); + rulesInserter.shiftRulesToMakeSpaceAt(ruleIdx, 1); + final Collection conditionsForNextRule = new ArrayList<>(); + for (final Iterator 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 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 { } return alb.addRules(Util.toArray(rules, new Rule[0])); } + + protected Iterable addShardingRules(ApplicationLoadBalancer alb, Iterable shardingKeys, + TargetGroup 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 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 { /** * Path conditions are constructed by pre-pending a "*" to the sharding key. */ + //TODO delete -> changed so ShardingRulesBuilder public static String getPathConditionForShardingKey(ShardingKey shardingKey, String pathPrefixForShardingKey) { return pathPrefixForShardingKey+shardingKey.toString(); } @@ -468,6 +479,7 @@ implements ProcedureCreatingLoadBalancerMapping { } protected ShardingKey getShardingKeyFromPathCondition(String path) { + // TODO change because we've got more than one condition per Sharding key return getShardingKeyFromPathCondition(path, pathPrefixForShardingKey); } }