SOLR-11205: Any metrics value can be directly accessed in autoscaling policies

This commit is contained in:
Noble Paul 2017-08-11 23:27:53 +09:30
parent 104578312e
commit 9a157edf5d
6 changed files with 174 additions and 46 deletions

View File

@ -76,6 +76,8 @@ New Features
* SOLR-11031: Implement SystemLogListener for autoscaling (ab)
* SOLR-11205: Any metrics value can be directly accessed in autoscaling policies (noble)
Bug Fixes
----------------------

View File

@ -30,6 +30,7 @@ import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.SolrClientDataProvider;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.cloud.OverseerTaskProcessor;
@ -48,6 +49,7 @@ import org.slf4j.LoggerFactory;
import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
import static org.apache.solr.common.cloud.ZkStateReader.SOLR_AUTOSCALING_CONF_PATH;
import static org.apache.solr.common.util.Utils.getObjectByPath;
@LuceneTestCase.Slow
public class TestPolicyCloud extends SolrCloudTestCase {
@ -135,6 +137,49 @@ public class TestPolicyCloud extends SolrCloudTestCase {
assertEquals("Expected exactly three replica of collection on node with port: " + secondNodePort, 3, replicasOnNode2);
}
public void testMetricsTag() throws Exception {
CloudSolrClient solrClient = cluster.getSolrClient();
String setClusterPolicyCommand = "{" +
" 'set-cluster-policy': [" +
" {'cores':'<10', 'node':'#ANY'}," +
" {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
" {'metrics:abc':'overseer', 'replica':0}" +
" ]" +
"}";
SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
try {
solrClient.request(req);
fail("expected exception");
} catch (HttpSolrClient.RemoteExecutionException e) {
// expected
assertTrue(String.valueOf(getObjectByPath(e.getMetaData(),
false, "error/details[0]/errorMessages[0]")).contains("Invalid metrics: param in"));
}
setClusterPolicyCommand = "{" +
" 'set-cluster-policy': [" +
" {'cores':'<10', 'node':'#ANY'}," +
" {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
" {'metrics:solr.node:ADMIN./admin/authorization.clientErrors:count':'>58768765', 'replica':0}" +
" ]" +
"}";
req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
solrClient.request(req);
//org.eclipse.jetty.server.handler.DefaultHandler.2xx-responses
CollectionAdminRequest.createCollection("metricsTest", "conf", 1, 1)
.process(cluster.getSolrClient());
DocCollection collection = getCollectionState("metricsTest");
SolrClientDataProvider provider = new SolrClientDataProvider(solrClient);
List<String> tags = Arrays.asList("metrics:solr.node:ADMIN./admin/authorization.clientErrors:count",
"metrics:solr.jvm:buffers.direct.Count");
Map<String, Object> val = provider.getNodeValues(collection .getReplicas().get(0).getNodeName(), tags);
for (String tag : tags) {
assertNotNull( "missing : "+ tag , val.get(tag));
}
}
public void testCreateCollectionWithPolicyAndMaxShardsPerNode() throws Exception {
CloudSolrClient solrClient = cluster.getSolrClient();
Map original = Utils.getJson(solrClient.getZkStateReader().getZkClient(), SOLR_AUTOSCALING_CONF_PATH, true);

View File

@ -84,6 +84,12 @@ public class Clause implements MapWriter, Comparable<Clause> {
}
if (tag == null)
throw new RuntimeException("Invalid op, must have one and only one tag other than collection, shard,replica " + Utils.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 " + Utils.toJSONString(m) + " must have at 2 or 3 segments after 'metrics:' separated by ':'");
}
}
}
@ -153,12 +159,18 @@ public class Clause implements MapWriter, Comparable<Clause> {
this.op = op;
}
public boolean isPass(Object inputVal) {
boolean isPass(Object inputVal) {
if (inputVal instanceof ReplicaCount) inputVal = ((ReplicaCount) inputVal).getVal(type);
return op.match(val, validate(name, inputVal, false)) == PASS;
ValidateInfo validator = getValidator(name);
if (validator == LazyValidator.INST) { // we don't know the type
return op.match(parseString(val), parseString(inputVal)) == PASS;
} else {
return op.match(val, validate(name, inputVal, false)) == PASS;
}
}
public boolean isPass(Row row) {
boolean isPass(Row row) {
return op.match(val, row.getVal(name)) == PASS;
}
@ -172,7 +184,13 @@ public class Clause implements MapWriter, Comparable<Clause> {
}
public Long delta(Object val) {
return op.delta(this.val, val);
if (this.val instanceof String) {
if (op == LESS_THAN || op == GREATER_THAN) {
return op.delta(Clause.parseDouble(name, this.val), Clause.parseDouble(name, val));
} else {
return 0l;
}
} else return op.delta(this.val, val);
}
public String getName() {
@ -376,8 +394,57 @@ public class Clause implements MapWriter, Comparable<Clause> {
if (max != null && !type.isInstance(max))
throw new RuntimeException("wrong max value type, expected: " + type.getName() + " actual: " + max.getClass().getName());
}
public Object validate(String name, Object val, boolean isRuleVal) {
if (type == Double.class) {
Double num = parseDouble(name, val);
if (isRuleVal) {
if (min != null)
if (Double.compare(num, (Double) min) == -1)
throw new RuntimeException(name + ": " + val + " must be greater than " + min);
if (max != null)
if (Double.compare(num, (Double) max) == 1)
throw new RuntimeException(name + ": " + val + " must be less than " + max);
}
return num;
} else if (type == Long.class) {
Long num = parseLong(name, val);
if (isRuleVal) {
if (min != null)
if (num < min.longValue())
throw new RuntimeException(name + ": " + val + " must be greater than " + min);
if (max != null)
if (num > max.longValue())
throw new RuntimeException(name + ": " + val + " must be less than " + max);
}
return num;
} else if (type == String.class) {
if (isRuleVal && vals != null && !vals.contains(val))
throw new RuntimeException(name + ": " + val + " must be one of " + StrUtils.join(vals, ','));
return val;
} else {
throw new RuntimeException("Invalid type ");
}
}
}
static class LazyValidator extends ValidateInfo {
static final LazyValidator INST = new LazyValidator();
LazyValidator() {
super(null, null, null, null);
}
@Override
public Object validate(String name, Object val, boolean isRuleVal) {
return parseString(val);
}
}
public static String parseString(Object val) {
return val == null ? null : String.valueOf(val);
}
/**
* @param name name of the condition
@ -385,40 +452,18 @@ public class Clause implements MapWriter, Comparable<Clause> {
* @param isRuleVal is this provided in the rule
* @return actual validated value
*/
public static Object validate(String name, Object val, boolean isRuleVal) {
public static Object validate(String name, Object val, boolean isRuleVal) {
if (val == null) return null;
ValidateInfo info = getValidator(name);
if (info == null) throw new RuntimeException("Unknown type :" + name);
return info.validate(name, val, isRuleVal);
}
private static ValidateInfo getValidator(String name) {
ValidateInfo info = validatetypes.get(name);
if (info == null && name.startsWith(ImplicitSnitch.SYSPROP)) info = validatetypes.get("STRING");
if (info == null) throw new RuntimeException("Unknown type :" + name);
if (info.type == Double.class) {
Double num = parseDouble(name, val);
if (isRuleVal) {
if (info.min != null)
if (Double.compare(num, (Double) info.min) == -1)
throw new RuntimeException(name + ": " + val + " must be greater than " + info.min);
if (info.max != null)
if (Double.compare(num, (Double) info.max) == 1)
throw new RuntimeException(name + ": " + val + " must be less than " + info.max);
}
return num;
} else if (info.type == Long.class) {
Long num = parseLong(name, val);
if (isRuleVal) {
if (info.min != null)
if (num < info.min.longValue())
throw new RuntimeException(name + ": " + val + " must be greater than " + info.min);
if (info.max != null)
if (num > info.max.longValue())
throw new RuntimeException(name + ": " + val + " must be less than " + info.max);
}
return num;
} else if (info.type == String.class) {
if (isRuleVal && info.vals != null && !info.vals.contains(val))
throw new RuntimeException(name + ": " + val + " must be one of " + StrUtils.join(info.vals, ','));
return val;
} else {
throw new RuntimeException("Invalid type ");
}
if (info == null && name.startsWith(METRICS_PREFIX)) info = LazyValidator.INST;
return info;
}
public static Long parseLong(String name, Object val) {
@ -467,6 +512,8 @@ public class Clause implements MapWriter, Comparable<Clause> {
throw new RuntimeException(name + ": " + val + "not a valid number");
}
public static final String METRICS_PREFIX = "metrics:";
private static final Map<String, ValidateInfo> validatetypes = new HashMap<>();
static {
@ -482,6 +529,7 @@ public class Clause implements MapWriter, Comparable<Clause> {
validatetypes.put("NUMBER", new ValidateInfo(Long.class, null, 0L, Long.MAX_VALUE));//generic number validation
validatetypes.put("STRING", new ValidateInfo(String.class, null, null, null));//generic string validation
validatetypes.put("node", new ValidateInfo(String.class, null, null, null));
validatetypes.put("LAZY", LazyValidator.INST);
for (String ip : ImplicitSnitch.IP_SNITCHES) validatetypes.put(ip, new ValidateInfo(Long.class, null, 0L, 255L));
}
}

View File

@ -58,6 +58,7 @@ public enum Operand {
@Override
public TestStatus match(Object ruleVal, Object testVal) {
if (testVal == null) return NOT_APPLICABLE;
if (ruleVal instanceof String) ruleVal = Clause.parseDouble("", ruleVal);
if (ruleVal instanceof Double) {
return Double.compare(Clause.parseDouble("", testVal), (Double) ruleVal) == 1 ? PASS : FAIL;
}
@ -73,6 +74,7 @@ public enum Operand {
@Override
public TestStatus match(Object ruleVal, Object testVal) {
if (testVal == null) return NOT_APPLICABLE;
if (ruleVal instanceof String) ruleVal = Clause.parseDouble("", ruleVal);
if (ruleVal instanceof Double) {
return Double.compare(Clause.parseDouble("", testVal), (Double) ruleVal) == -1 ? PASS : FAIL;
}
@ -93,10 +95,6 @@ public enum Operand {
this.priority = priority;
}
public String toStr(Object expectedVal) {
return operand + expectedVal.toString();
}
public TestStatus match(Object ruleVal, Object testVal) {
return Objects.equals(ruleVal, testVal) ? PASS : FAIL;
}

View File

@ -32,6 +32,7 @@ import java.util.Set;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.cloud.autoscaling.Clause;
import org.apache.solr.client.solrj.cloud.autoscaling.ClusterDataProvider;
import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
import org.apache.solr.client.solrj.request.GenericSolrRequest;
@ -54,6 +55,8 @@ import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.client.solrj.cloud.autoscaling.Clause.METRICS_PREFIX;
/**
* Class that implements {@link ClusterStateProvider} accepting a SolrClient
*/
@ -165,6 +168,7 @@ public class SolrClientDataProvider implements ClusterDataProvider, MapWriter {
protected void getRemoteInfo(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
ClientSnitchCtx snitchContext = (ClientSnitchCtx) ctx;
readSysProps(solrNode, requestedTags, snitchContext);
readMetrics(solrNode, requestedTags, snitchContext);
Set<String> groups = new HashSet<>();
List<String> prefixes = new ArrayList<>();
if (requestedTags.contains(DISK)) {
@ -217,6 +221,34 @@ public class SolrClientDataProvider implements ClusterDataProvider, MapWriter {
}
}
private void readMetrics(String solrNode, Set<String> requestedTags, ClientSnitchCtx snitchContext) {
List<String> metricsNames = null;
for (String tag : requestedTags) {
if (tag.startsWith(METRICS_PREFIX)) {
if (metricsNames == null) metricsNames = new ArrayList<>();
metricsNames.add(tag);
}
}
if (metricsNames == null) return;
for (String metricsName : metricsNames) {
List<String> ss = StrUtils.splitSmart(metricsName, ':');
if (ss.size() < 3 || ss.size() > 4) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid metrics name" + metricsName);
}
try {
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("group", ss.get(1));
params.add("prefix", ss.get(2));
if (ss.size() == 4) params.add("property", ss.get(3));
SimpleSolrResponse rsp = snitchContext.invoke(solrNode, CommonParams.METRICS_PATH, params);
Object v = Utils.getObjectByPath(rsp.nl, true, ss);
if (v != null) snitchContext.getTags().put(metricsName, v);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "", e);
}
}
}
private void readSysProps(String solrNode, Set<String> requestedTags, ClientSnitchCtx snitchContext) {
List<String> prefixes = null;
ModifiableSolrParams params;
@ -237,9 +269,8 @@ public class SolrClientDataProvider implements ClusterDataProvider, MapWriter {
for (String s : sysProp) params.add("property", s);
try {
SimpleSolrResponse rsp = snitchContext.invoke(solrNode, CommonParams.METRICS_PATH, params);
Map m = rsp.nl.asMap(6);
for (String s : sysProp) {
Object v = Utils.getObjectByPath(m, true,
Object v = Utils.getObjectByPath(rsp.nl, true,
Arrays.asList("metrics", "solr.jvm", "system.properties", s));
if (v != null) snitchContext.getTags().put("sysprop." + s, v);
}

View File

@ -31,14 +31,12 @@ import java.util.Map;
import com.google.common.collect.ImmutableList;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.impl.SolrClientDataProvider;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.cloud.autoscaling.Clause.Violation;
import org.apache.solr.client.solrj.cloud.autoscaling.Policy.Suggester.Hint;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.ReplicaPosition;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.Utils;
import org.apache.solr.common.util.ValidatingJsonMap;
@ -189,6 +187,14 @@ public class TestPolicy extends SolrTestCaseJ4 {
assertTrue(c.tag.isPass(12.8d));
assertFalse(c.tag.isPass("12.6"));
assertFalse(c.tag.isPass(12.6d));
c = new Clause((Map<String, Object>) Utils.fromJSONString("{replica:0, 'metrics:x:y:z':'>12.7'}"));
assertTrue(c.tag.val instanceof String);
assertTrue(c.tag.isPass("12.8"));
assertTrue(c.tag.isPass(12.8d));
assertFalse(c.tag.isPass("12.6"));
assertFalse(c.tag.isPass(12.6d));
}
public void testNodeLost() {
@ -1201,8 +1207,6 @@ public class TestPolicy extends SolrTestCaseJ4 {
suggester = suggester.getSession().getSuggester(MOVEREPLICA).hint(Hint.TARGET_NODE, "10.0.0.6:7574_solr");
op = suggester.getOperation();
assertNull(op);
}