mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-24 14:38:45 +00:00
bug5787: interims backup commit, starting to extract rule inserting logic
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-1
@@ -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
|
||||
|
||||
+21
-7
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+202
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user