| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.solr.client.solrj.cloud.autoscaling; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| import org.apache.solr.client.solrj.cloud.autoscaling.Variable.Type; |
| import org.apache.solr.common.MapWriter; |
| import org.apache.solr.common.cloud.Replica; |
| import org.apache.solr.common.util.StrUtils; |
| import org.apache.solr.common.util.Utils; |
| |
| import static java.util.Collections.singletonMap; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Operand.EQUAL; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Operand.GREATER_THAN; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Operand.LESS_THAN; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Operand.NOT_EQUAL; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Operand.RANGE_EQUAL; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Operand.WILDCARD; |
| import static org.apache.solr.client.solrj.cloud.autoscaling.Policy.ANY; |
| import static org.apache.solr.common.params.CoreAdminParams.COLLECTION; |
| import static org.apache.solr.common.params.CoreAdminParams.NODE; |
| import static org.apache.solr.common.params.CoreAdminParams.REPLICA; |
| import static org.apache.solr.common.params.CoreAdminParams.SHARD; |
| import static org.apache.solr.common.util.StrUtils.formatString; |
| import static org.apache.solr.common.util.Utils.toJSONString; |
| |
| /** |
| * Represents a set of conditions in the policy |
| * |
| * @deprecated to be removed in Solr 9.0 (see SOLR-14656) |
| */ |
| public class Clause implements MapWriter, Comparable<Clause> { |
| public static final String NODESET = "nodeset"; |
| static final Set<String> IGNORE_TAGS = new HashSet<>(Arrays.asList(REPLICA, COLLECTION, SHARD, "strict", "type", "put", NODESET)); |
| |
| private final int hashCode; |
| final boolean hasComputedValue; |
| final Map<String, Object> original; |
| final Clause derivedFrom; |
| boolean nodeSetPresent = false; |
| Condition collection, shard, replica, tag, globalTag; |
| final Replica.Type type; |
| Put put; |
| boolean strict; |
| |
| protected Clause(Clause clause, Function<Condition, Object> computedValueEvaluator) { |
| this.original = clause.original; |
| this.hashCode = original.hashCode(); |
| this.type = clause.type; |
| this.put = clause.put; |
| this.nodeSetPresent = clause.nodeSetPresent; |
| this.collection = clause.collection; |
| this.shard = clause.shard; |
| this.tag = evaluateValue(clause.tag, computedValueEvaluator); |
| this.replica = evaluateValue(clause.replica, computedValueEvaluator); |
| this.globalTag = evaluateValue(clause.globalTag, computedValueEvaluator); |
| this.hasComputedValue = clause.hasComputedValue; |
| this.strict = clause.strict; |
| derivedFrom = clause.derivedFrom; |
| } |
| |
| // internal use only |
| Clause(Map<String, Object> original, Condition tag, Condition globalTag, boolean isStrict, Put put, boolean nodeSetPresent) { |
| this.hashCode = original.hashCode(); |
| this.original = original; |
| this.tag = tag; |
| this.globalTag = globalTag; |
| this.globalTag.clause = this; |
| this.type = null; |
| this.hasComputedValue = false; |
| this.strict = isStrict; |
| derivedFrom = null; |
| this.put = put; |
| this.nodeSetPresent = nodeSetPresent; |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| private Clause(Map<String, Object> m) { |
| derivedFrom = (Clause) m.remove(Clause.class.getName()); |
| this.original = Utils.getDeepCopy(m, 10); |
| this.hashCode = original.hashCode(); |
| String type = (String) m.get("type"); |
| this.type = type == null || ANY.equals(type) ? null : Replica.Type.valueOf(type.toUpperCase(Locale.ROOT)); |
| String put = (String) m.getOrDefault("put", m.containsKey(NODESET)? Put.ON_ALL.val: null ); |
| if (put != null) { |
| this.put = Put.get(put); |
| if (this.put == null) throwExp(m, "invalid value for put : {0}", put); |
| } |
| |
| strict = Boolean.parseBoolean(String.valueOf(m.getOrDefault("strict", "true"))); |
| Optional<String> globalTagName = m.keySet().stream().filter(Policy.GLOBAL_ONLY_TAGS::contains).findFirst(); |
| if (globalTagName.isPresent()) { |
| if (m.size() > 2) { |
| // 3 keys are allowed only if one of them is 'strict' |
| if (!m.containsKey("strict") || m.size() > 3) { |
| throw new RuntimeException("Only, 'strict' and one extra tag supported for the tag " + globalTagName.get() + " in " + toJSONString(m)); |
| } |
| } |
| globalTag = parse(globalTagName.get(), m); |
| tag = parse(m.keySet().stream() |
| .filter(s -> (!globalTagName.get().equals(s) && !IGNORE_TAGS.contains(s))) |
| .findFirst().get(), m); |
| } else { |
| collection = parse(COLLECTION, m); |
| shard = parse(SHARD, m); |
| if (m.get(REPLICA) == null) { |
| throw new IllegalArgumentException(formatString("'replica' is required in {0}", toJSONString(m))); |
| } |
| this.replica = parse(REPLICA, m); |
| if (replica.op == WILDCARD) throw new IllegalArgumentException("replica val cannot be null" + toJSONString(m)); |
| |
| this.nodeSetPresent = parseNodeset(m); |
| m.forEach((s, o) -> parseCondition(s, o, m)); |
| } |
| if (tag == null) |
| throw new RuntimeException("Invalid op, must have one and only one tag other than collection, shard,replica " + toJSONString(m)); |
| if (tag.name.startsWith(Clause.METRICS_PREFIX)) { |
| List<String> ss = StrUtils.splitSmart(tag.name, ':'); |
| if (ss.size() < 3 || ss.size() > 4) { |
| throw new RuntimeException("Invalid metrics: param in " + toJSONString(m) + " must have at 2 or 3 segments after 'metrics:' separated by ':'"); |
| } |
| } |
| doPostValidate(collection, shard, replica, tag, globalTag); |
| hasComputedValue = hasComputedValue(); |
| } |
| |
| private boolean parseNodeset(Map<String, Object> m) { |
| if (!m.containsKey(NODESET)) return false; |
| Object o = m.get(NODESET); |
| if (o instanceof Map) { |
| String key = validateObjectInNodeset(m, (Map) o); |
| parseCondition(key, o, m); |
| } else if (o instanceof List) { |
| @SuppressWarnings({"rawtypes"}) |
| List l = (List) o; |
| if(l.size()<2) throwExp(m, "nodeset [] must have atleast 2 items"); |
| if( checkMapArray(l, m)) return true; |
| for (Object it : l) { |
| if (it instanceof String) continue; |
| else throwExp(m, "nodeset :[]must have only string values"); |
| } |
| parseCondition("node", o, m); |
| } else { |
| throwExp(m, "invalid value for nodeset, must be an object or a list of String"); |
| } |
| return true; |
| } |
| |
| private String validateObjectInNodeset(@SuppressWarnings({"rawtypes"})Map<String, Object> m, |
| @SuppressWarnings({"rawtypes"})Map map) { |
| if (map.size() != 1) { |
| throwExp(m, "nodeset must only have one and only one key"); |
| } |
| String key = (String) map.keySet().iterator().next(); |
| Object val = map.get(key); |
| if(val instanceof String && ((String )val).trim().charAt(0) == '#'){ |
| throwExp(m, formatString("computed value {0} not allowed in nodeset", val)); |
| } |
| return key; |
| } |
| |
| private boolean checkMapArray(@SuppressWarnings({"rawtypes"})List l, Map<String, Object> m) { |
| @SuppressWarnings({"rawtypes"}) |
| List<Map> maps = null; |
| for (Object o : l) { |
| if (o instanceof Map) { |
| if (maps == null) maps = new ArrayList<>(); |
| maps.add((Map) o); |
| } |
| } |
| String key = null; |
| if (maps != null) { |
| if (maps.size() != l.size()) throwExp(m, "all elements of nodeset must be Objects"); |
| List<Condition> tags = new ArrayList<>(maps.size()); |
| for (@SuppressWarnings({"rawtypes"})Map map : maps) { |
| String s = validateObjectInNodeset(m, map); |
| if(key == null) key = s; |
| if(!Objects.equals(key, s)){ |
| throwExp(m, "all element must have same key"); |
| } |
| tags.add(parse(s, m)); |
| } |
| if(this.put == Put.ON_EACH) throwExp(m, "cannot use put: ''on-each-node'' with an array value in nodeset "); |
| this.tag = new Condition(key, tags,Operand.IN, null,this); |
| return true; |
| } |
| return false; |
| |
| } |
| |
| public Condition getThirdTag() { |
| return globalTag == null ? tag : globalTag; |
| } |
| |
| private void doPostValidate(Condition... conditions) { |
| for (Condition condition : conditions) { |
| if (condition == null) continue; |
| String err = condition.varType.postValidate(condition); |
| if (err != null) { |
| throw new IllegalArgumentException(formatString("Error in clause : {0}, caused by : {1}", toJSONString(original), err)); |
| } |
| } |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| public static Clause create(String json) { |
| return create((Map<String, Object>) Utils.fromJSONString(json)); |
| } |
| |
| public static Clause create(Map<String, Object> m) { |
| Clause clause = new Clause(m); |
| return clause.hasComputedValue() ? |
| clause : |
| clause.getSealedClause(null); |
| } |
| |
| public static String parseString(Object val) { |
| if (val instanceof Condition) val = ((Condition) val).val; |
| return val == null ? null : String.valueOf(val); |
| } |
| |
| public Condition getCollection() { |
| return collection; |
| } |
| |
| public Condition getShard() { |
| return shard; |
| } |
| |
| public Condition getReplica() { |
| return replica; |
| } |
| |
| public Condition getTag() { |
| return tag; |
| } |
| |
| public Condition getGlobalTag() { |
| return globalTag; |
| } |
| |
| Condition evaluateValue(Condition condition, Function<Condition, Object> computedValueEvaluator) { |
| if (condition == null) return null; |
| if (condition.computedType == null) return condition; |
| Object val = computedValueEvaluator.apply(condition); |
| val = condition.op.readRuleValue(new Condition(condition.name, val, condition.op, null, null)); |
| return new Condition(condition.name, val, condition.op, null, this); |
| } |
| |
| public boolean doesOverride(Clause that) { |
| return (collection.equals(that.collection) && |
| tag.name.equals(that.tag.name)); |
| |
| } |
| |
| public boolean isPerCollectiontag() { |
| return globalTag == null; |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| void parseCondition(String s, Object o, @SuppressWarnings({"rawtypes"})Map m) { |
| if (IGNORE_TAGS.contains(s)) return; |
| if (tag != null) { |
| throwExp(m, "Only one tag other than collection, shard, replica is possible"); |
| } |
| tag = parse(s, o instanceof Map? (Map<String, Object>) o : singletonMap(s, o)); |
| } |
| |
| private int compareTypes(Replica.Type t1, Replica.Type t2) { |
| if (t1 == null && t2 == null) return 0; |
| if (t1 != null && t2 == null) return -1; |
| if (t1 == null) return 1; |
| return 0; |
| } |
| |
| private boolean hasComputedValue() { |
| if (replica != null && replica.computedType != null) return true; |
| if (tag != null && tag.computedType != null) return true; |
| if (globalTag != null && globalTag.computedType != null) return true; |
| return false; |
| |
| } |
| |
| @Override |
| public int compareTo(Clause that) { |
| int v = Integer.compare(this.tag.op.priority, that.tag.op.priority); |
| if (v != 0) return v; |
| if (this.isPerCollectiontag() && that.isPerCollectiontag()) { |
| v = Integer.compare(this.replica.op.priority, that.replica.op.priority); |
| if (v == 0) {// higher the number of replicas , harder to satisfy |
| Double thisVal = this.replica.val instanceof RangeVal ? ((RangeVal) this.replica.val).max.doubleValue() : (Double) this.replica.val; |
| Double thatVal = that.replica.val instanceof RangeVal ? ((RangeVal) that.replica.val).max.doubleValue() : (Double) that.replica.val; |
| v = Preference.compareWithTolerance(thisVal, thatVal, 1); |
| v = this.replica.op == LESS_THAN ? v : v * -1; |
| } |
| if (v == 0) v = compareTypes(this.type, that.type); |
| return v; |
| } else { |
| return 0; |
| } |
| } |
| |
| void addTags(Collection<String> params) { |
| if (globalTag != null && !params.contains(globalTag.name)) params.add(globalTag.name); |
| if (tag != null && !params.contains(tag.name)) params.add(tag.name); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof Clause)) return false; |
| Clause that = (Clause) o; |
| return Objects.equals(this.original, that.original); |
| } |
| |
| //replica value is zero |
| boolean isReplicaZero() { |
| return replica != null && replica.getOperand() == Operand.EQUAL && replica.val instanceof Double && |
| Preference.compareWithTolerance(0d, (Double) replica.val, 1) == 0; |
| } |
| |
| public SealedClause getSealedClause(Function<Condition, Object> computedValueEvaluator) { |
| return this instanceof SealedClause ? |
| (SealedClause) this : |
| new SealedClause(this, computedValueEvaluator); |
| } |
| |
| Condition parse(String s, Map<String, Object> m) { |
| |
| Object expectedVal = null; |
| ComputedType computedType = null; |
| Object val = m.get(s); |
| Type varType = VariableBase.getTagType(s); |
| if (varType.meta.isHidden()) { |
| throwExp(m, "''{0}'' is not allowed", varType.tagName); |
| } |
| try { |
| String conditionName = s.trim(); |
| Operand operand = null; |
| if (val == null) { |
| operand = WILDCARD; |
| expectedVal = Policy.ANY; |
| } else if (val instanceof List) { |
| if (!varType.meta.supportArrayVals()) { |
| throwExp(m, "array values are not supported for {0}", conditionName); |
| } |
| expectedVal = readListVal(m, (List) val, varType, conditionName); |
| operand = Operand.IN; |
| } else if (val instanceof String) { |
| String strVal = ((String) val).trim(); |
| val = strVal; |
| operand = getOperand(strVal); |
| strVal = strVal.substring(Operand.EQUAL == operand || WILDCARD == operand ? 0 : 1); |
| for (ComputedType t : ComputedType.values()) { |
| String changedVal = t.match(strVal); |
| if (changedVal != null) { |
| computedType = t; |
| strVal = changedVal; |
| if (varType == null || !varType.supportedComputedTypes.contains(computedType)) { |
| throwExp(m, "''{0}'' is not allowed for variable : ''{1}''", t, conditionName); |
| } |
| } |
| } |
| if (computedType == null && ((String) val).charAt(0) == '#' && !varType.wildCards.contains(val)) { |
| throwExp(m, "''{0}'' is not an allowed value for ''{1}'', supported value is : {2} ", val, conditionName, varType.wildCards); |
| |
| } |
| operand = varType == null ? operand : varType.getOperand(operand, strVal, computedType); |
| expectedVal = validate(s, new Condition(s, strVal, operand, computedType, null), true); |
| |
| } else if (val instanceof Number) { |
| operand = Operand.EQUAL; |
| operand = varType.getOperand(operand, val, null); |
| expectedVal = validate(s, new Condition(s, val, operand, null, null), true); |
| } |
| return new Condition(conditionName, expectedVal, operand, computedType, this); |
| |
| } catch (IllegalArgumentException iae) { |
| throw iae; |
| } catch (Exception e) { |
| throwExp(m, " Invalid tag : {0} "+ e.getMessage(), s); |
| return null; |
| } |
| } |
| |
| public static void throwExp(@SuppressWarnings({"rawtypes"})Map clause, String msg, Object... args) { |
| throw new IllegalArgumentException("syntax error in clause :" + toJSONString(clause) + " , msg: " + formatString(msg, args)); |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private static List readListVal(Map m, List val, Type varType, String conditionName) { |
| List list = val; |
| list = (List) list.stream() |
| .map(it -> varType.validate(conditionName, it, true)) |
| .map(it -> { |
| if (it instanceof String) { |
| String trim = ((String) it).trim(); |
| if (trim.isEmpty()) |
| throw new IllegalArgumentException(formatString("{0} cannot have an empty string value in clause : {1}", |
| conditionName, toJSONString(m))); |
| return trim; |
| } else return it; |
| }).filter(it -> it == null ? false : true) |
| .collect(Collectors.toList()); |
| if (list.isEmpty()) |
| throw new IllegalArgumentException(formatString("{0} cannot have an empty list value in clause : {1}", |
| conditionName, toJSONString(m))); |
| for (Object o : list) { |
| if (o instanceof String) { |
| if (getOperand((String) o) != EQUAL) { |
| throw new IllegalArgumentException(formatString("No operators are supported in collection values in condition : {0} in clause : {1}", |
| conditionName, toJSONString(m))); |
| } |
| } |
| } |
| if (list.size() < 2) { |
| throw new IllegalArgumentException(formatString("Array should have more than one value in condition : {0} in clause : {1}", |
| conditionName, toJSONString(m))); |
| |
| } |
| return list; |
| } |
| |
| private static Operand getOperand(String strVal) { |
| Operand operand; |
| if (Policy.ANY.equals(strVal) || Policy.EACH.equals(strVal)) operand = WILDCARD; |
| else if (strVal.startsWith(NOT_EQUAL.operand)) operand = NOT_EQUAL; |
| else if (strVal.startsWith(GREATER_THAN.operand)) operand = GREATER_THAN; |
| else if (strVal.startsWith(LESS_THAN.operand)) operand = LESS_THAN; |
| else operand = Operand.EQUAL; |
| return operand; |
| } |
| |
| |
| private boolean isRowPass(ComputedValueEvaluator eval, Object t, Row row) { |
| eval.node = row.node; |
| if (t instanceof Condition) { |
| Condition tag = (Condition) t; |
| if (tag.computedType != null) tag = evaluateValue(tag, eval); |
| return tag.isPass(row); |
| } else { |
| return t.equals(row.getVal(tag.name)); |
| } |
| } |
| |
| List<Violation> testGroupNodes(Policy.Session session, double[] deviations) { |
| //e.g: {replica:'#EQUAL', shard:'#EACH', sysprop.zone:'#EACH'} |
| ComputedValueEvaluator eval = new ComputedValueEvaluator(session); |
| eval.collName = (String) collection.getValue(); |
| Violation.Ctx ctx = new Violation.Ctx(this, session.matrix, eval); |
| |
| @SuppressWarnings({"rawtypes"}) |
| Set tags = getUniqueTags(session, eval); |
| if (tags.isEmpty()) return Collections.emptyList(); |
| |
| Set<String> shards = getShardNames(session, eval); |
| |
| for (String s : shards) { |
| final ReplicaCount replicaCount = new ReplicaCount(); |
| eval.shardName = s; |
| |
| for (Object tag : tags) { |
| replicaCount.reset(); |
| for (Row row : session.matrix) { |
| if(!isRowPass(eval, tag, row)) continue; |
| addReplicaCountsForNode(eval, replicaCount, row); |
| } |
| |
| SealedClause sealedClause = this.getSealedClause(eval); |
| if (!sealedClause.replica.isPass(replicaCount)) { |
| ReplicaCount replicaCountCopy = replicaCount.copy(); |
| Violation violation = new Violation(sealedClause, |
| eval.collName, |
| eval.shardName, |
| null, |
| replicaCountCopy, |
| sealedClause.getReplica().replicaCountDelta(replicaCountCopy), |
| tag); |
| ctx.resetAndAddViolation(tag, replicaCountCopy, violation); |
| sealedClause.addViolatingReplicasForGroup(sealedClause.tag, eval, ctx, this.tag.name, tag, violation, session.matrix); |
| if (!this.strict && deviations != null) { |
| this.tag.varType.computeDeviation(session, deviations, replicaCount, sealedClause); |
| } |
| } else { |
| if (replica.op == RANGE_EQUAL) this.tag.varType.computeDeviation(session, deviations, replicaCount, sealedClause); |
| } |
| } |
| } |
| return ctx.allViolations; |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private Set getUniqueTags(Policy.Session session, ComputedValueEvaluator eval) { |
| Set tags = new HashSet(); |
| |
| if(nodeSetPresent) { |
| if (tag.val instanceof List && ((List) tag.val).get(0) instanceof Condition) { |
| tags.addAll((List) tag.val); |
| } else { |
| tags.add(tag); |
| } |
| return tags; |
| } |
| if(tag.op == WILDCARD){ |
| for (Row row : session.matrix) { |
| eval.node = row.node; |
| Condition tag = this.tag; |
| if (tag.computedType != null) tag = evaluateValue(tag, eval); |
| Object val = row.getVal(tag.name); |
| if (val != null) { |
| if (tag.op == LESS_THAN || tag.op == GREATER_THAN) { |
| tags.add(this.tag); |
| } else if (tag.isPass(val)) { |
| tags.add(val); |
| } |
| } |
| } |
| |
| } else { |
| |
| if (tag.op == LESS_THAN || tag.op == GREATER_THAN || tag.op == RANGE_EQUAL || tag.op == NOT_EQUAL) { |
| tags.add(tag); // eg: freedisk > 100 |
| } else if (tag.val instanceof Collection) { |
| tags.add(tag); //e: sysprop.zone:[east,west] |
| } else { |
| tags.add(tag.val);// |
| } |
| } |
| return tags; |
| } |
| |
| void addViolatingReplicasForGroup(Condition tag, |
| ComputedValueEvaluator eval, |
| Violation.Ctx ctx, String tagName, Object tagVal, |
| Violation violation, |
| List<Row> nodes) { |
| if (tag.varType.addViolatingReplicas(ctx)) return; |
| for (Row row : nodes) { |
| boolean isPass; |
| if (tagVal instanceof Condition) { |
| Condition condition = (Condition) tagVal; |
| if(condition.computedType != null){ |
| eval.node = row.node; |
| Object val = eval.apply(condition); |
| condition = new Condition(condition.name, val,condition.op, null, condition.clause); |
| } |
| isPass = condition.isPass(row); |
| } |
| else isPass = tagVal.equals(row.getVal(tagName)); |
| if (isPass) { |
| row.forEachReplica(eval.collName, ri -> { |
| if (Policy.ANY.equals(eval.shardName) |
| || eval.shardName.equals(ri.getShard())) |
| violation.addReplica(new Violation.ReplicaInfoAndErr(ri).withDelta(tag.delta(row.getVal(tag.name)))); |
| }); |
| } |
| } |
| |
| } |
| |
| public static long addReplicaCountsForNode = 0; |
| public static long addReplicaCountsForNodeCacheMiss = 0; |
| public static final String PERSHARD_REPLICAS = Clause.class.getSimpleName() + ".perShardReplicas"; |
| |
| private void addReplicaCountsForNode(ComputedValueEvaluator computedValueEvaluator, ReplicaCount replicaCount, Row node) { |
| addReplicaCountsForNode++; |
| |
| ReplicaCount rc = node.computeCacheIfAbsent(computedValueEvaluator.collName, computedValueEvaluator.shardName, PERSHARD_REPLICAS, |
| this, o -> { |
| addReplicaCountsForNodeCacheMiss++; |
| ReplicaCount result = new ReplicaCount(); |
| node.forEachReplica((String) collection.getValue(), ri -> { |
| if (Policy.ANY.equals(computedValueEvaluator.shardName) |
| || computedValueEvaluator.shardName.equals(ri.getShard())) |
| result.increment(ri); |
| }); |
| return result; |
| }); |
| if (rc != null) |
| replicaCount.increment(rc); |
| |
| |
| } |
| |
| List<Violation> testPerNode(Policy.Session session, double[] deviations) { |
| ComputedValueEvaluator eval = new ComputedValueEvaluator(session); |
| eval.collName = (String) collection.getValue(); |
| Violation.Ctx ctx = new Violation.Ctx(this, session.matrix, eval); |
| Set<String> shards = getShardNames(session, eval); |
| for (String s : shards) { |
| final ReplicaCount replicaCount = new ReplicaCount(); |
| eval.shardName = s; |
| for (Row row : session.matrix) { |
| replicaCount.reset(); |
| eval.node = row.node; |
| Condition tag = this.tag; |
| if (tag.computedType != null) { |
| tag = evaluateValue(tag, eval); |
| } |
| if (!tag.isPass(row)) continue; |
| addReplicaCountsForNode(eval, replicaCount, row); |
| SealedClause sealedClause = this.getSealedClause(eval); |
| if (!sealedClause.replica.isPass(replicaCount)) { |
| ReplicaCount replicaCountCopy = replicaCount.copy(); |
| Violation violation = new Violation(sealedClause, |
| eval.collName, |
| eval.shardName, |
| eval.node, |
| replicaCountCopy, |
| sealedClause.getReplica().replicaCountDelta(replicaCountCopy), |
| eval.node); |
| ctx.resetAndAddViolation(row.node, replicaCountCopy, violation); |
| sealedClause.addViolatingReplicasForGroup(sealedClause.tag, eval, ctx, NODE, row.node, violation, |
| Collections.singletonList(row)); |
| if (!this.strict && deviations != null) { |
| tag.varType.computeDeviation(session, deviations, replicaCount, sealedClause); |
| } |
| } else { |
| if (replica.op == RANGE_EQUAL) tag.varType.computeDeviation(session, deviations, replicaCount, sealedClause); |
| } |
| } |
| } |
| return ctx.allViolations; |
| } |
| |
| private Set<String> getShardNames(Policy.Session session, |
| ComputedValueEvaluator eval) { |
| Set<String> shards = new HashSet<>(); |
| if (isShardAbsent()) { |
| shards.add(Policy.ANY); //consider the entire collection is a single shard |
| } else { |
| for (Row row : session.matrix) { |
| row.forEachShard(eval.collName, (shard, r) -> { |
| if (this.shard.isPass(shard)) shards.add(shard); // add relevant shards |
| }); |
| } |
| } |
| return shards; |
| } |
| |
| boolean isShardAbsent() { |
| return Policy.ANY.equals(shard.val); |
| } |
| |
| public List<Violation> test(Policy.Session session, double[] deviations) { |
| if (isPerCollectiontag()) { |
| if(nodeSetPresent) { |
| if(put == Put.ON_EACH){ |
| return testPerNode(session, deviations) ; |
| } else { |
| return testGroupNodes(session, deviations); |
| } |
| } |
| |
| return tag.varType == Type.NODE || |
| (tag.varType.meta.isNodeSpecificVal() && replica.computedType == null) ? |
| testPerNode(session, deviations) : |
| testGroupNodes(session, deviations); |
| } else { |
| ComputedValueEvaluator computedValueEvaluator = new ComputedValueEvaluator(session); |
| Violation.Ctx ctx = new Violation.Ctx(this, session.matrix, computedValueEvaluator); |
| for (Row r : session.matrix) { |
| computedValueEvaluator.node = r.node; |
| SealedClause sealedClause = getSealedClause(computedValueEvaluator); |
| if (r.isLive() && !sealedClause.getGlobalTag().isPass(r)) { |
| ctx.resetAndAddViolation(r.node, null, new Violation(sealedClause, null, null, r.node, r.getVal(sealedClause.globalTag.name), |
| sealedClause.globalTag.delta(r.getVal(globalTag.name)), r.node)); |
| addViolatingReplicasForGroup(sealedClause.globalTag, computedValueEvaluator, ctx, Type.CORES.tagName, r.node, ctx.currentViolation, session.matrix); |
| |
| } |
| } |
| return ctx.allViolations; |
| |
| } |
| } |
| |
| |
| public boolean isMatch(ReplicaInfo r, String collection, String shard) { |
| if (type != null && r.getType() != type) return false; |
| if (r.getCollection().equals(collection)) { |
| if (this.shard == null || this.shard.val.equals(Policy.ANY)) return true; |
| else if (this.shard.val.equals(Policy.EACH) && r.getShard().equals(shard)) return true; |
| else return this.shard.val.equals(r.getShard()) && r.getShard().equals(shard); |
| } |
| return false; |
| } |
| |
| boolean matchShard(String replicaShard, String shardInContext) { |
| if (shard == null || shard.val.equals(ANY)) return true; |
| if (shard.val.equals(Policy.EACH) && replicaShard.equals(shardInContext)) return true; |
| if (shard.val.equals(replicaShard)) return true; |
| return false; |
| } |
| |
| public boolean isStrict() { |
| return strict; |
| } |
| |
| @Override |
| public String toString() { |
| return toJSONString(original); |
| } |
| |
| @Override |
| public void writeMap(EntryWriter ew) throws IOException { |
| for (Map.Entry<String, Object> e : original.entrySet()) ew.put(e.getKey(), e.getValue()); |
| } |
| |
| enum TestStatus { |
| NOT_APPLICABLE, FAIL, PASS |
| } |
| |
| public static class ComputedValueEvaluator implements Function<Condition, Object> { |
| final Policy.Session session; |
| String collName = null; |
| String shardName = null; |
| String node = null; |
| |
| public ComputedValueEvaluator(Policy.Session session) { |
| this.session = session; |
| } |
| |
| @Override |
| public Object apply(Condition computedCondition) { |
| return computedCondition.varType.computeValue(session, computedCondition, collName, shardName, node); |
| } |
| |
| } |
| |
| /** |
| * @param name name of the condition |
| * @param val value of the condition |
| * @param isRuleVal is this provided in the rule |
| * @return actual validated value |
| */ |
| public static Object validate(String name, Object val, boolean isRuleVal) { |
| if (val == null) return null; |
| Type info = VariableBase.getTagType(name); |
| if (info == null) throw new RuntimeException("Unknown type :" + name); |
| return info.validate(name, val, isRuleVal); |
| } |
| |
| public static Long parseLong(String name, Object val) { |
| if (val == null) return null; |
| if (val instanceof Long) return (Long) val; |
| Number num = null; |
| if (val instanceof String) { |
| try { |
| num = Long.parseLong(((String) val).trim()); |
| } catch (NumberFormatException e) { |
| try { |
| num = Double.parseDouble((String) val); |
| } catch (NumberFormatException e1) { |
| throw new RuntimeException(name + ": " + val + "not a valid number", e); |
| } |
| } |
| |
| } else if (val instanceof Number) { |
| num = (Number) val; |
| } |
| |
| if (num != null) { |
| return num.longValue(); |
| } |
| throw new RuntimeException(name + ": " + val + "not a valid number"); |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashCode; |
| } |
| |
| public static Double parseDouble(String name, Object val) { |
| if (val == null) return null; |
| if (val instanceof RangeVal) val = ((RangeVal) val).actual; |
| if (val instanceof Double) return (Double) val; |
| Number num = null; |
| if (val instanceof String) { |
| try { |
| num = Double.parseDouble((String) val); |
| } catch (NumberFormatException e) { |
| throw new RuntimeException(name + ": " + val + "not a valid number", e); |
| } |
| |
| } else if (val instanceof Number) { |
| num = (Number) val; |
| } |
| |
| if (num != null) { |
| return num.doubleValue(); |
| } |
| throw new RuntimeException(name + ": " + val + "not a valid number"); |
| } |
| |
| public static final String METRICS_PREFIX = "metrics:"; |
| |
| enum Put { |
| ON_ALL(""), ON_EACH("on-each-node"); |
| |
| public final String val; |
| |
| Put(String s) { |
| this.val = s; |
| } |
| |
| public static Put get(String s) { |
| for (Put put : values()) { |
| if (put.val.equals(s)) return put; |
| } |
| return null; |
| } |
| } |
| |
| } |