Mercurial > stress-tester
changeset 1016:d25e9c9f0e28
Filters generalization in progress
author | Devel 2 |
---|---|
date | Wed, 25 Mar 2020 11:42:32 +0100 |
parents | 518e5e10d714 |
children | f8c9c14d507f |
files | stress-tester/src/main/java/com/passus/st/client/filter/SequenceFilter.java stress-tester/src/main/java/com/passus/st/client/filter/SequenceListener.java stress-tester/src/main/java/com/passus/st/client/http/ReporterDestination.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceFilter.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceListener.java stress-tester/src/test/java/com/passus/st/client/filter/SequenceFilterTest.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpSequenceFilterTest.java |
diffstat | 7 files changed, 661 insertions(+), 653 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stress-tester/src/main/java/com/passus/st/client/filter/SequenceFilter.java Wed Mar 25 11:42:32 2020 +0100 @@ -0,0 +1,482 @@ +package com.passus.st.client.filter; + +import com.passus.commons.Assert; +import com.passus.commons.annotations.Plugin; +import com.passus.commons.metric.MapMetric; +import com.passus.commons.service.Registry; +import com.passus.commons.time.SystemTimeGenerator; +import com.passus.commons.time.TimeAware; +import com.passus.commons.time.TimeGenerator; +import com.passus.config.*; +import com.passus.config.annotations.NodeDefinitionCreate; +import com.passus.config.schema.*; +import com.passus.config.validation.Errors; +import com.passus.filter.ValueExtractor; +import com.passus.filter.ValueExtractorParser; +import com.passus.filter.config.PredicateNodeTransformer; +import com.passus.st.client.FlowContext; +import com.passus.st.client.http.ReporterDestination; +import com.passus.st.filter.Transformers; +import com.passus.st.plugin.PluginConstants; + +import java.io.Serializable; +import java.text.ParseException; +import java.util.*; +import java.util.function.Predicate; + +import static com.passus.config.schema.ConfigurationSchemaBuilder.*; + +/** + * @author mikolaj.podbielski + */ +@NodeDefinitionCreate(SequenceFilter.NodeDefCreator.class) +@Plugin(name = SequenceFilter.TYPE, category = PluginConstants.CATEGORY_FLOW_FILTER) +public class SequenceFilter implements FlowFilter, TimeAware { + + public static class SequenceItem { + + private final Predicate predicate; + private boolean mustOccur = true; + private long time = 10_000; + private int num; + private String alias; + private String[] aliases; + + public SequenceItem(Predicate predicate, int num) { + this(predicate); + this.num = num; + } + + public SequenceItem(Predicate predicate) { + if (predicate == null) { + throw new NullPointerException(); + } + this.predicate = predicate; + } + + public Predicate getPredicate() { + return predicate; + } + + public int getNum() { + return num; + } + + public void setNum(int num) { + this.num = num; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + if (time <= 0) { + throw new IllegalArgumentException("Time must be greater than zero."); + } + this.time = time; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + aliases = null; + } + + public boolean isMustOccur() { + return mustOccur; + } + + public void setMustOccur(boolean mustOccur) { + this.mustOccur = mustOccur; + } + + private boolean match(Map<String, Object> value) { + return predicate.test(value); + } + + private String[] getAliases() { + if (aliases == null) { + if (alias != null) { + aliases = new String[2]; + aliases[1] = alias; + } else { + aliases = new String[1]; + } + + aliases[0] = "_i" + num; + } + + return aliases; + } + } + + private static class SeqChainItem { + + private final SequenceItem seq; + private SeqChainItem next; + private final long startTime; + private final long endTime; + private boolean active = true; + + public SeqChainItem(SequenceItem seq, long startTime) { + this.seq = seq; + this.startTime = startTime; + this.endTime = startTime + seq.time; + } + + public void setNext(SeqChainItem next) { + this.next = next; + } + + public long getStartTime() { + return startTime; + } + + public long getEndTime() { + return endTime; + } + + public boolean match(Map<String, Object> value) { + return seq.match(value); + } + + public SeqChainItem getNext() { + return next; + } + + public boolean inTimeRange(long time) { + return (startTime <= time && time < endTime); + } + } + + private static class SeqChain { + + private SeqChainItem seqItem; + + private final Map<String, Object> valueMap; +// private Filterable value = new FilterableMap(); +// private FlowMarker flowMarker; + + public SeqChain(SequenceItem[] seqItems, long startTime, Map<String, Object> valueMap) { + this.valueMap = valueMap; + + SeqChainItem curItem = new SeqChainItem(seqItems[1], startTime); + seqItem = curItem; + + for (int i = 2; i < seqItems.length; i++) { + SeqChainItem nexItem = new SeqChainItem(seqItems[i], curItem.getStartTime()); + curItem.setNext(nexItem); + curItem = nexItem; + } + } + + public void updateValue(MessageWrapper wrapper) { + valueMap.put("req", wrapper.getReq()); + valueMap.put("resp", wrapper.getResp()); + } + + public void persistsValue(MessageWrapper wrapper, String[] aliases) { + for (String alias : aliases) { + valueMap.put(alias, wrapper); + } + } + + public boolean hasNext() { + return (seqItem != null && seqItem.getNext() != null); + } + + public boolean next() { + if (seqItem == null) { + return false; + } + + seqItem = seqItem.getNext(); + return true; + } + + public boolean rewind(long time) { + if (seqItem == null) { + return false; + } + + while (hasNext()) { + if (seqItem.inTimeRange(time)) { + return true; + } else if (seqItem.seq.mustOccur) { + return false; + } + + next(); + } + + return false; + } + } + + public static final String TYPE = "sequence"; + + private final List<SeqChain> chains = new ArrayList<>(); + private SequenceListener listener = (e) -> { + }; + private SequenceItem[] seqItems; + private Map<String, ValueExtractor> values = Collections.EMPTY_MAP; + + private final FlowFilterFactory filterFactory; + + private TimeGenerator timeGenerator = new SystemTimeGenerator(); + + public SequenceFilter() { + this(FlowFilterFactory.DEFAULT_FACTORY); + } + + public SequenceFilter(FlowFilterFactory filterFactory) { + Assert.notNull(filterFactory, "filterFactory"); + this.filterFactory = filterFactory; + } + + public SequenceListener getListener() { + return listener; + } + + public void setListener(SequenceListener listener) { + this.listener = listener; + } + + public SequenceItem[] getSeqItems() { + return seqItems; + } + + public void setSeqItems(SequenceItem[] seqItems) { + this.seqItems = seqItems; + } + + public Map<String, ValueExtractor> getValues() { + return values; + } + + public void setValues(Map<String, ValueExtractor> values) { + this.values = values; + } + + @Override + public TimeGenerator getTimeGenerator() { + return timeGenerator; + } + + @Override + public void setTimeGenerator(TimeGenerator timeGenerator) { + Assert.notNull(timeGenerator, "timeGenerator"); + this.timeGenerator = timeGenerator; + } + + private void processValue(long now, MessageWrapper value) { + Iterator<SeqChain> iterator = chains.iterator(); + while (iterator.hasNext()) { + SeqChain chain = iterator.next(); + + if (chain.seqItem.startTime <= now) { + if (!chain.seqItem.inTimeRange(now) && !chain.rewind(now)) { + chain.persistsValue(value, chain.seqItem.seq.getAliases()); + fireEvent(chain); + iterator.remove(); + } else if (chain.seqItem.active) { + chain.updateValue(value); + + if (chain.seqItem.match(chain.valueMap)) { + if (!chain.seqItem.seq.mustOccur) { + iterator.remove(); + } else { + chain.persistsValue(value, chain.seqItem.seq.getAliases()); + if (!chain.hasNext()) { + fireEvent(chain); + iterator.remove(); + } else if (!chain.next()) { + fireEvent(chain); + iterator.remove(); + } + } + } + } + } + } + + final Map<String, Object> valueMap = new HashMap<>(); + valueMap.put("req", value.getReq()); + valueMap.put("resp", value.getResp()); + + if (seqItems[0].match(valueMap)) { + SeqChain chain = new SeqChain(seqItems, now, valueMap); + chain.persistsValue(value, seqItems[0].getAliases()); + chains.add(chain); + } + } + + private void fireEvent(SeqChain chain) { + Map<String, Serializable> result; + + if (values.isEmpty()) { + result = Collections.emptyMap(); + } else { + result = new HashMap<>(); + Map<String, Object> persisted = chain.valueMap; + for (Map.Entry<String, ValueExtractor> e : values.entrySet()) { + Object value = e.getValue().extract(persisted); + if (value instanceof Serializable) { + result.put(e.getKey(), (Serializable) value); + } + } + } + + listener.sequenceDetected(new MapMetric("sequence", result)); + } + + @Override + public int filterInbound(Object req, Object resp, FlowContext context) { + MessageWrapper wrapper = filterFactory.createWrapper(req, resp, context); + processValue(timeGenerator.currentTimeMillis(), wrapper); + return DUNNO; + } + + @Override + public void reset() { + chains.clear(); + } + + @Override + public SequenceFilter instanceForWorker(int index) { + SequenceFilter filter = new SequenceFilter(); + filter.listener = listener; + filter.seqItems = seqItems; + filter.values = values; + return filter; + } + + @Override + public void configure(Configuration config, ConfigurationContext context) { + List<SequenceItem> itemsList = (List<SequenceItem>) config.get("sequence"); + seqItems = itemsList.toArray(new SequenceItem[itemsList.size()]); + for (int i = 0; i < seqItems.length; ++i) { + seqItems[i].setNum(i); + } + + values = (Map<String, ValueExtractor>) config.get("values", Collections.EMPTY_MAP); + ReporterDestination reporterDestination = Registry.getInstance().get( + ReporterDestination.SERVICE_NAME, ReporterDestination.class); + if (reporterDestination != null) { + listener = reporterDestination; + } + } + + public static class NodeDefCreator implements NodeDefinitionCreator { + + @Override + public NodeDefinition create() { + MapNodeDefinition elementDef = mapDef( + tupleDef("match", new MessagePredicateNodeDefinition()), + tupleDef("mustOccur", valueDef(Boolean.class)).setRequired(false), + tupleDef("time", valueDef(Long.class)).setRequired(false), + tupleDef("alias", valueDef()).setRequired(false) + ); + elementDef.setTransformer(new SequencesNodeTransformer()); + // TODO: mustOccur and time required in all steps but first + + return mapDef( + tupleDef("sequence", new ListNodeDefinition(elementDef).setMinNumberOfValues(2)), + tupleDef("values", new MappingNodeDefinition().setTransformer(new ValuesNodeTransformer())).setRequired(false) + ); + } + + } + + private static class SequencesNodeTransformer implements NodeTransformer<CNode> { + + private static final PredicateNodeTransformer TRANSFORMER = Transformers.PREDICATE; + + @Override + public CNode transform(CNode node, Errors errors, ConfigurationContext context) { + try { + CMapNode mapNode = (CMapNode) node; + List<CTupleNode> tupleNodes = mapNode.getChildren(); + + Predicate predicate = null; + boolean mustOccur = true; + long time = 0; + String alias = null; + for (CTupleNode tupleNode : tupleNodes) { + String name = tupleNode.getName(); + CNode valueNode = tupleNode.getNode(); + switch (name) { + case "match": + predicate = TRANSFORMER.transform(valueNode); + break; + case "mustOccur": + mustOccur = (Boolean) getValue(valueNode); + break; + case "time": + time = (Long) getValue(valueNode); + break; + case "alias": + alias = (String) getValue(valueNode); + break; + } + } + SequenceItem item = new SequenceItem(predicate); + if (time > 0) { + item.setTime(time); + } + item.setMustOccur(mustOccur); + item.setAlias(alias); + return new CValueNode(item); + } catch (Exception ex) { + return node; + } + } + + @Override + public CNode reverseTransform(CNode node, Errors errors, ConfigurationContext context) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } + + private static class ValuesNodeTransformer implements NodeTransformer<CNode> { + + @Override + public CNode transform(CNode node, Errors errors, ConfigurationContext context) { + Map<String, ValueExtractor> result = new HashMap<>(); + + CMapNode mapNode = (CMapNode) node; + List<CTupleNode> tupleNodes = mapNode.getChildren(); + for (CTupleNode tupleNode : tupleNodes) { + String name = tupleNode.getName(); + CValueNode valueNode = (CValueNode) tupleNode.getNode(); + String value = (String) valueNode.getValue(); + try { + errors.pushNestedPath(name); + ValueExtractor extractor = ValueExtractorParser.DEFAULT.parse(value); + result.put(name, extractor); + } catch (ParseException ex) { + errors.reject(tupleNode, "Invalid expression: \"%s\"", value); + } finally { + errors.popNestedPath(); + } + } + return new CValueNode(result); + } + + @Override + public CNode reverseTransform(CNode node, Errors errors, ConfigurationContext context) { + throw new UnsupportedOperationException("Impossible."); // ValueExtractor + } + + } + + private static Object getValue(CNode node) { + CValueNode valueNode = (CValueNode) node; + return valueNode.getValue(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stress-tester/src/main/java/com/passus/st/client/filter/SequenceListener.java Wed Mar 25 11:42:32 2020 +0100 @@ -0,0 +1,12 @@ +package com.passus.st.client.filter; + +import com.passus.commons.metric.MapMetric; + +/** + * + * @author mikolaj.podbielski + */ +public interface SequenceListener { + + void sequenceDetected(MapMetric sequence); +}
--- a/stress-tester/src/main/java/com/passus/st/client/http/ReporterDestination.java Wed Mar 25 11:32:36 2020 +0100 +++ b/stress-tester/src/main/java/com/passus/st/client/http/ReporterDestination.java Wed Mar 25 11:42:32 2020 +0100 @@ -9,7 +9,7 @@ import com.passus.net.http.HttpMessage; import com.passus.st.client.ClientListener; import com.passus.st.client.FlowContext; -import com.passus.st.client.http.filter.HttpSequenceListener; +import com.passus.st.client.filter.SequenceListener; import com.passus.st.metric.MetricsCollectionHandler; import java.util.Arrays; @@ -20,7 +20,7 @@ /** * @author mikolaj.podbielski */ -public interface ReporterDestination extends Service, ClientListener, HttpSequenceListener, MetricsCollectionHandler, Configurable { +public interface ReporterDestination extends Service, ClientListener, SequenceListener, MetricsCollectionHandler, Configurable { public static final String SERVICE_NAME = "ReporterDestination";
--- a/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceFilter.java Wed Mar 25 11:32:36 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,471 +0,0 @@ -package com.passus.st.client.http.filter; - -import com.passus.commons.Assert; -import com.passus.commons.annotations.Plugin; -import com.passus.commons.metric.MapMetric; -import com.passus.commons.service.Registry; -import com.passus.config.*; -import com.passus.config.annotations.NodeDefinitionCreate; -import com.passus.config.schema.*; -import com.passus.config.validation.Errors; -import com.passus.filter.ValueExtractor; -import com.passus.filter.ValueExtractorParser; -import com.passus.filter.config.PredicateNodeTransformer; -import com.passus.net.http.HttpRequest; -import com.passus.net.http.HttpResponse; -import com.passus.st.client.FlowContext; -import com.passus.st.client.filter.FlowFilterFactory; -import com.passus.st.client.filter.MessagePredicateNodeDefinition; -import com.passus.st.client.filter.MessageWrapper; -import com.passus.st.client.http.ReporterDestination; -import com.passus.st.filter.Transformers; -import com.passus.st.plugin.PluginConstants; - -import java.io.Serializable; -import java.text.ParseException; -import java.util.*; -import java.util.function.Predicate; - -import static com.passus.config.schema.ConfigurationSchemaBuilder.*; - -/** - * @author mikolaj.podbielski - */ -@NodeDefinitionCreate(HttpSequenceFilter.NodeDefCreator.class) -@Plugin(name = HttpSequenceFilter.TYPE, category = PluginConstants.CATEGORY_FLOW_FILTER) -public class HttpSequenceFilter extends HttpFilter { - - public static class SequenceItem { - - private final Predicate predicate; - private boolean mustOccur = true; - private long time = 10_000; - private int num; - private String alias; - private String[] aliases; - - public SequenceItem(Predicate predicate, int num) { - this(predicate); - this.num = num; - } - - public SequenceItem(Predicate predicate) { - if (predicate == null) { - throw new NullPointerException(); - } - this.predicate = predicate; - } - - public Predicate getPredicate() { - return predicate; - } - - public int getNum() { - return num; - } - - public void setNum(int num) { - this.num = num; - } - - public long getTime() { - return time; - } - - public void setTime(long time) { - if (time <= 0) { - throw new IllegalArgumentException("Time must be greater than zero."); - } - this.time = time; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - aliases = null; - } - - public boolean isMustOccur() { - return mustOccur; - } - - public void setMustOccur(boolean mustOccur) { - this.mustOccur = mustOccur; - } - - private boolean match(Map<String, Object> value) { - return predicate.test(value); - } - - private String[] getAliases() { - if (aliases == null) { - if (alias != null) { - aliases = new String[2]; - aliases[1] = alias; - } else { - aliases = new String[1]; - } - - aliases[0] = "_i" + num; - } - - return aliases; - } - } - - private static class SeqChainItem { - - private final SequenceItem seq; - private SeqChainItem next; - private final long startTime; - private final long endTime; - private boolean active = true; - - public SeqChainItem(SequenceItem seq, long startTime) { - this.seq = seq; - this.startTime = startTime; - this.endTime = startTime + seq.time; - } - - public void setNext(SeqChainItem next) { - this.next = next; - } - - public long getStartTime() { - return startTime; - } - - public long getEndTime() { - return endTime; - } - - public boolean match(Map<String, Object> value) { - return seq.match(value); - } - - public SeqChainItem getNext() { - return next; - } - - public boolean inTimeRange(long time) { - return (startTime <= time && time < endTime); - } - } - - private static class SeqChain { - - private SeqChainItem seqItem; - - private final Map<String, Object> valueMap; -// private Filterable value = new FilterableMap(); -// private FlowMarker flowMarker; - - public SeqChain(SequenceItem[] seqItems, long startTime, Map<String, Object> valueMap) { - this.valueMap = valueMap; - - SeqChainItem curItem = new SeqChainItem(seqItems[1], startTime); - seqItem = curItem; - - for (int i = 2; i < seqItems.length; i++) { - SeqChainItem nexItem = new SeqChainItem(seqItems[i], curItem.getStartTime()); - curItem.setNext(nexItem); - curItem = nexItem; - } - } - - public void updateValue(MessageWrapper wrapper) { - valueMap.put("req", wrapper.getReq()); - valueMap.put("resp", wrapper.getResp()); - } - - public void persistsValue(MessageWrapper wrapper, String[] aliases) { - for (String alias : aliases) { - valueMap.put(alias, wrapper); - } - } - - public boolean hasNext() { - return (seqItem != null && seqItem.getNext() != null); - } - - public boolean next() { - if (seqItem == null) { - return false; - } - - seqItem = seqItem.getNext(); - return true; - } - - public boolean rewind(long time) { - if (seqItem == null) { - return false; - } - - while (hasNext()) { - if (seqItem.inTimeRange(time)) { - return true; - } else if (seqItem.seq.mustOccur) { - return false; - } - - next(); - } - - return false; - } - } - - public static final String TYPE = "sequence"; - - private final List<SeqChain> chains = new ArrayList<>(); - private HttpSequenceListener listener = (e) -> { - }; - private SequenceItem[] seqItems; - private Map<String, ValueExtractor> values = Collections.EMPTY_MAP; - - private final FlowFilterFactory filterFactory; - - public HttpSequenceFilter() { - this(FlowFilterFactory.DEFAULT_FACTORY); - } - - public HttpSequenceFilter(FlowFilterFactory filterFactory) { - Assert.notNull(filterFactory, "filterFactory"); - this.filterFactory = filterFactory; - } - - public HttpSequenceListener getListener() { - return listener; - } - - public void setListener(HttpSequenceListener listener) { - this.listener = listener; - } - - public SequenceItem[] getSeqItems() { - return seqItems; - } - - public void setSeqItems(SequenceItem[] seqItems) { - this.seqItems = seqItems; - } - - public Map<String, ValueExtractor> getValues() { - return values; - } - - public void setValues(Map<String, ValueExtractor> values) { - this.values = values; - } - - private void processValue(long now, MessageWrapper value) { - Iterator<SeqChain> iterator = chains.iterator(); - while (iterator.hasNext()) { - SeqChain chain = iterator.next(); - - if (chain.seqItem.startTime <= now) { - if (!chain.seqItem.inTimeRange(now) && !chain.rewind(now)) { - chain.persistsValue(value, chain.seqItem.seq.getAliases()); - fireEvent(chain); - iterator.remove(); - } else if (chain.seqItem.active) { - chain.updateValue(value); - - if (chain.seqItem.match(chain.valueMap)) { - if (!chain.seqItem.seq.mustOccur) { - iterator.remove(); - } else { - chain.persistsValue(value, chain.seqItem.seq.getAliases()); - if (!chain.hasNext()) { - fireEvent(chain); - iterator.remove(); - } else if (!chain.next()) { - fireEvent(chain); - iterator.remove(); - } - } - } - } - } - } - - final Map<String, Object> valueMap = new HashMap<>(); - valueMap.put("req", value.getReq()); - valueMap.put("resp", value.getResp()); - - if (seqItems[0].match(valueMap)) { - SeqChain chain = new SeqChain(seqItems, now, valueMap); - chain.persistsValue(value, seqItems[0].getAliases()); - chains.add(chain); - } - } - - private void fireEvent(SeqChain chain) { - Map<String, Serializable> result; - - if (values.isEmpty()) { - result = Collections.emptyMap(); - } else { - result = new HashMap<>(); - Map<String, Object> persisted = chain.valueMap; - for (Map.Entry<String, ValueExtractor> e : values.entrySet()) { - Object value = e.getValue().extract(persisted); - if (value instanceof Serializable) { - result.put(e.getKey(), (Serializable) value); - } - } - } - - listener.sequenceDetected(new MapMetric("sequence", result)); - } - - @Override - public int filterInbound(HttpRequest req, HttpResponse resp, FlowContext context) { - MessageWrapper wrapper = filterFactory.createWrapper(req, resp, context); - processValue(resp.getTimestamp(), wrapper); - return DUNNO; - } - - @Override - public void reset() { - chains.clear(); - } - - @Override - public HttpSequenceFilter instanceForWorker(int index) { - HttpSequenceFilter filter = new HttpSequenceFilter(); - filter.listener = listener; - filter.seqItems = seqItems; - filter.values = values; - return filter; - } - - @Override - public void configure(Configuration config, ConfigurationContext context) { - List<SequenceItem> itemsList = (List<SequenceItem>) config.get("sequence"); - seqItems = itemsList.toArray(new SequenceItem[itemsList.size()]); - for (int i = 0; i < seqItems.length; ++i) { - seqItems[i].setNum(i); - } - - values = (Map<String, ValueExtractor>) config.get("values", Collections.EMPTY_MAP); - ReporterDestination reporterDestination = Registry.getInstance().get( - ReporterDestination.SERVICE_NAME, ReporterDestination.class); - if (reporterDestination != null) { - listener = reporterDestination; - } - } - - public static class NodeDefCreator implements NodeDefinitionCreator { - - @Override - public NodeDefinition create() { - MapNodeDefinition elementDef = mapDef( - tupleDef("match", new MessagePredicateNodeDefinition()), - tupleDef("mustOccur", valueDef(Boolean.class)).setRequired(false), - tupleDef("time", valueDef(Long.class)).setRequired(false), - tupleDef("alias", valueDef()).setRequired(false) - ); - elementDef.setTransformer(new SequencesNodeTransformer()); - // TODO: mustOccur and time required in all steps but first - - return mapDef( - tupleDef("sequence", new ListNodeDefinition(elementDef).setMinNumberOfValues(2)), - tupleDef("values", new MappingNodeDefinition().setTransformer(new ValuesNodeTransformer())).setRequired(false) - ); - } - - } - - private static class SequencesNodeTransformer implements NodeTransformer<CNode> { - - private static final PredicateNodeTransformer TRANSFORMER = Transformers.PREDICATE; - - @Override - public CNode transform(CNode node, Errors errors, ConfigurationContext context) { - try { - CMapNode mapNode = (CMapNode) node; - List<CTupleNode> tupleNodes = mapNode.getChildren(); - - Predicate predicate = null; - boolean mustOccur = true; - long time = 0; - String alias = null; - for (CTupleNode tupleNode : tupleNodes) { - String name = tupleNode.getName(); - CNode valueNode = tupleNode.getNode(); - switch (name) { - case "match": - predicate = TRANSFORMER.transform(valueNode); - break; - case "mustOccur": - mustOccur = (Boolean) getValue(valueNode); - break; - case "time": - time = (Long) getValue(valueNode); - break; - case "alias": - alias = (String) getValue(valueNode); - break; - } - } - SequenceItem item = new SequenceItem(predicate); - if (time > 0) { - item.setTime(time); - } - item.setMustOccur(mustOccur); - item.setAlias(alias); - return new CValueNode(item); - } catch (Exception ex) { - return node; - } - } - - @Override - public CNode reverseTransform(CNode node, Errors errors, ConfigurationContext context) { - throw new UnsupportedOperationException("Not supported yet."); - } - - } - - private static class ValuesNodeTransformer implements NodeTransformer<CNode> { - - @Override - public CNode transform(CNode node, Errors errors, ConfigurationContext context) { - Map<String, ValueExtractor> result = new HashMap<>(); - - CMapNode mapNode = (CMapNode) node; - List<CTupleNode> tupleNodes = mapNode.getChildren(); - for (CTupleNode tupleNode : tupleNodes) { - String name = tupleNode.getName(); - CValueNode valueNode = (CValueNode) tupleNode.getNode(); - String value = (String) valueNode.getValue(); - try { - errors.pushNestedPath(name); - ValueExtractor extractor = ValueExtractorParser.DEFAULT.parse(value); - result.put(name, extractor); - } catch (ParseException ex) { - errors.reject(tupleNode, "Invalid expression: \"%s\"", value); - } finally { - errors.popNestedPath(); - } - } - return new CValueNode(result); - } - - @Override - public CNode reverseTransform(CNode node, Errors errors, ConfigurationContext context) { - throw new UnsupportedOperationException("Impossible."); // ValueExtractor - } - - } - - private static Object getValue(CNode node) { - CValueNode valueNode = (CValueNode) node; - return valueNode.getValue(); - } -}
--- a/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceListener.java Wed Mar 25 11:32:36 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -package com.passus.st.client.http.filter; - -import com.passus.commons.metric.MapMetric; - -/** - * - * @author mikolaj.podbielski - */ -public interface HttpSequenceListener { - - void sequenceDetected(MapMetric sequence); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stress-tester/src/test/java/com/passus/st/client/filter/SequenceFilterTest.java Wed Mar 25 11:42:32 2020 +0100 @@ -0,0 +1,165 @@ +package com.passus.st.client.filter; + +import com.passus.commons.metric.MapMetric; +import com.passus.commons.utils.ResourceUtils; +import com.passus.config.validation.Errors; +import com.passus.filter.AndPredicate; +import com.passus.filter.BeanValueExtractor; +import com.passus.filter.ComparisonOperator; +import com.passus.filter.ComparisonPredicate; +import com.passus.filter.UnmutableValueExtractor; +import com.passus.filter.ValueExtractor; +import com.passus.filter.ValueExtractorParser; +import com.passus.filter.config.PredicateNodeTransformer; +import com.passus.net.http.HttpRequest; +import com.passus.net.http.HttpRequestBuilder; +import com.passus.net.http.HttpResponse; +import com.passus.net.http.HttpResponseBuilder; +import com.passus.st.AppUtils; +import com.passus.st.client.filter.SequenceFilter.SequenceItem; +import com.passus.st.filter.Transformers; +import java.io.File; +import java.io.Serializable; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import static org.testng.AssertJUnit.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * + * @author mikolaj.podbielski + */ +public class SequenceFilterTest { + + private static final PredicateNodeTransformer TRANSFORMER = Transformers.PREDICATE; + + @BeforeClass + public static void beforeClass() { + AppUtils.registerAll(); + } + + @AfterClass + public static void afterClass() { + AppUtils.unregisterAll(); + } + + @Test + public void testFilterInbound() throws Exception { + HttpRequest req0 = HttpRequestBuilder.get("http://example.com/res1").build(); + HttpResponse resp0 = HttpResponseBuilder.ok().cookie("id", "123").build(); + + HttpRequest req1 = HttpRequestBuilder.get("http://example.com/res2") + .cookie("id", "123").header("Xyz", "abc").build(); + HttpResponse resp1 = HttpResponseBuilder.ok().build(); + + TestSequenceListener listener = new TestSequenceListener(); + + SequenceItem[] seqItems = { + item("{\"@req.uri\": \"/res1\", \"@resp.getCookie('id')\": {$neq: \"0\"}}", 0), + item("{\"@req.getHeader('xyz')\": \"abc\", \"@req.getCookie('id')\": \"@_i0.resp.getCookie('id')\"}", 1, "last"),}; + + Map<String, ValueExtractor> values = new HashMap<>(); + values.put("code", value("@_i0.resp.status.code")); + values.put("xhost", value("@_i0.req.url.host")); + values.put("header", value("@_i1.req.getHeader('xyz')")); + values.put("cookie", value("@last.req.getCookie('id')")); + values.put("seqName", value("example sequence")); + + SequenceFilter filter = new SequenceFilter().instanceForWorker(0); + filter.setListener(listener); + filter.setSeqItems(seqItems); + filter.setValues(values); + + filter.filterInbound(req0, resp0, null); + filter.filterInbound(req1, resp1, null); + + assertEquals(1, listener.events.size()); + Map<String, Serializable> extracted = listener.events.get(0).getAttributesValue(); + assertEquals(200, extracted.get("code")); + assertEquals("example.com", extracted.get("xhost").toString()); + assertEquals("abc", extracted.get("header").toString()); + assertEquals("123", extracted.get("cookie").toString()); + assertEquals("example sequence", extracted.get("seqName").toString()); + } + + private static SequenceItem item(String predicateString, int num) throws Exception { + Predicate predicate = TRANSFORMER.transform(predicateString); + return new SequenceItem(predicate, num); + } + + private static SequenceItem item(String predicateString, int num, String alias) throws Exception { + Predicate predicate = TRANSFORMER.transform(predicateString); + SequenceItem si = new SequenceItem(predicate, num); + si.setAlias(alias); + return si; + } + + private static ValueExtractor value(String s) throws ParseException { + return ValueExtractorParser.DEFAULT.parse(s); + } + + @Test + public void testConfigure() throws Exception { + File file = ResourceUtils.getFile("com/passus/st/client/http/filter/sequence.yml"); + String filterConfig = new String(Files.readAllBytes(Paths.get(file.toURI()))); + + Errors errors = new Errors(); + List<FlowFilter> filters = FlowFiltersConfigurator.getFilters(filterConfig, errors, null); + FilterTestUtils.printErrors(errors); + + assertEquals(0, errors.getErrorCount()); + assertEquals(1, filters.size()); + assertTrue(filters.get(0) instanceof SequenceFilter); + SequenceFilter filter = (SequenceFilter) filters.get(0); + + SequenceItem[] seqItems = filter.getSeqItems(); + assertEquals(2, seqItems.length); + assertSeqItemValue(seqItems[0], 0, 10000, null); + assertSeqItemValue(seqItems[1], 1, 20000, "last"); + + AndPredicate p0 = (AndPredicate) seqItems[0].getPredicate(); + assertEquals(2, p0.getSubPredicates().size()); + ComparisonPredicate sp1 = (ComparisonPredicate) p0.getSubPredicates().get(1); + assertEquals(ComparisonOperator.NOT_EQUAL, sp1.getOperator()); + + assertEquals("resp.getCookie('id')", sp1.getFieldName()); + + UnmutableValueExtractor uve = (UnmutableValueExtractor) sp1.getPattern(); + assertEquals(123, uve.extract(null)); + + Map<String, ValueExtractor> values = filter.getValues(); + assertEquals(4, values.size()); + ValueExtractor extractor = values.get("header"); + assertTrue(extractor instanceof BeanValueExtractor); + BeanValueExtractor bve = (BeanValueExtractor) extractor; + assertEquals("last.req.getHeader('xyz')", bve.getFieldName()); + } + + private static void assertSeqItemValue(SequenceItem item, int num, long time, String alias) { + assertEquals("num", num, item.getNum()); + assertEquals("time", time, item.getTime()); + assertEquals("alias", alias, item.getAlias()); + } + + public static class TestSequenceListener implements SequenceListener { + + ArrayList<MapMetric> events = new ArrayList<>(); + + @Override + public void sequenceDetected(MapMetric sequence) { + events.add(sequence); + } + + public void reset() { + events.clear(); + } + } +}
--- a/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpSequenceFilterTest.java Wed Mar 25 11:32:36 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -package com.passus.st.client.http.filter; - -import com.passus.commons.metric.MapMetric; -import com.passus.commons.utils.ResourceUtils; -import com.passus.config.validation.Errors; -import com.passus.filter.AndPredicate; -import com.passus.filter.BeanValueExtractor; -import com.passus.filter.ComparisonOperator; -import com.passus.filter.ComparisonPredicate; -import com.passus.filter.UnmutableValueExtractor; -import com.passus.filter.ValueExtractor; -import com.passus.filter.ValueExtractorParser; -import com.passus.filter.config.PredicateNodeTransformer; -import com.passus.net.http.HttpRequest; -import com.passus.net.http.HttpRequestBuilder; -import com.passus.net.http.HttpResponse; -import com.passus.net.http.HttpResponseBuilder; -import com.passus.st.AppUtils; -import com.passus.st.client.filter.FilterTestUtils; -import com.passus.st.client.filter.FlowFilter; -import com.passus.st.client.filter.FlowFiltersConfigurator; -import com.passus.st.client.http.filter.HttpSequenceFilter.SequenceItem; -import com.passus.st.filter.Transformers; -import java.io.File; -import java.io.Serializable; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import static org.testng.AssertJUnit.*; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -/** - * - * @author mikolaj.podbielski - */ -public class HttpSequenceFilterTest { - - private static final PredicateNodeTransformer TRANSFORMER = Transformers.PREDICATE; - - @BeforeClass - public static void beforeClass() { - AppUtils.registerAll(); - } - - @AfterClass - public static void afterClass() { - AppUtils.unregisterAll(); - } - - @Test - public void testFilterInbound() throws Exception { - HttpRequest req0 = HttpRequestBuilder.get("http://example.com/res1").build(); - HttpResponse resp0 = HttpResponseBuilder.ok().cookie("id", "123").build(); - - HttpRequest req1 = HttpRequestBuilder.get("http://example.com/res2") - .cookie("id", "123").header("Xyz", "abc").build(); - HttpResponse resp1 = HttpResponseBuilder.ok().build(); - - TestHttpSequenceListener listener = new TestHttpSequenceListener(); - - SequenceItem[] seqItems = { - item("{\"@req.uri\": \"/res1\", \"@resp.getCookie('id')\": {$neq: \"0\"}}", 0), - item("{\"@req.getHeader('xyz')\": \"abc\", \"@req.getCookie('id')\": \"@_i0.resp.getCookie('id')\"}", 1, "last"),}; - - Map<String, ValueExtractor> values = new HashMap<>(); - values.put("code", value("@_i0.resp.status.code")); - values.put("xhost", value("@_i0.req.url.host")); - values.put("header", value("@_i1.req.getHeader('xyz')")); - values.put("cookie", value("@last.req.getCookie('id')")); - values.put("seqName", value("example sequence")); - - HttpSequenceFilter filter = new HttpSequenceFilter().instanceForWorker(0); - filter.setListener(listener); - filter.setSeqItems(seqItems); - filter.setValues(values); - - filter.filterInbound(req0, resp0, null); - filter.filterInbound(req1, resp1, null); - - assertEquals(1, listener.events.size()); - Map<String, Serializable> extracted = listener.events.get(0).getAttributesValue(); - assertEquals(200, extracted.get("code")); - assertEquals("example.com", extracted.get("xhost").toString()); - assertEquals("abc", extracted.get("header").toString()); - assertEquals("123", extracted.get("cookie").toString()); - assertEquals("example sequence", extracted.get("seqName").toString()); - } - - private static SequenceItem item(String predicateString, int num) throws Exception { - Predicate predicate = TRANSFORMER.transform(predicateString); - return new SequenceItem(predicate, num); - } - - private static SequenceItem item(String predicateString, int num, String alias) throws Exception { - Predicate predicate = TRANSFORMER.transform(predicateString); - SequenceItem si = new SequenceItem(predicate, num); - si.setAlias(alias); - return si; - } - - private static ValueExtractor value(String s) throws ParseException { - return ValueExtractorParser.DEFAULT.parse(s); - } - - @Test - public void testConfigure() throws Exception { - File file = ResourceUtils.getFile("com/passus/st/client/http/filter/sequence.yml"); - String filterConfig = new String(Files.readAllBytes(Paths.get(file.toURI()))); - - Errors errors = new Errors(); - List<FlowFilter> filters = FlowFiltersConfigurator.getFilters(filterConfig, errors, null); - FilterTestUtils.printErrors(errors); - - assertEquals(0, errors.getErrorCount()); - assertEquals(1, filters.size()); - assertTrue(filters.get(0) instanceof HttpSequenceFilter); - HttpSequenceFilter filter = (HttpSequenceFilter) filters.get(0); - - SequenceItem[] seqItems = filter.getSeqItems(); - assertEquals(2, seqItems.length); - assertSeqItemValue(seqItems[0], 0, 10000, null); - assertSeqItemValue(seqItems[1], 1, 20000, "last"); - - AndPredicate p0 = (AndPredicate) seqItems[0].getPredicate(); - assertEquals(2, p0.getSubPredicates().size()); - ComparisonPredicate sp1 = (ComparisonPredicate) p0.getSubPredicates().get(1); - assertEquals(ComparisonOperator.NOT_EQUAL, sp1.getOperator()); - - assertEquals("resp.getCookie('id')", sp1.getFieldName()); - - UnmutableValueExtractor uve = (UnmutableValueExtractor) sp1.getPattern(); - assertEquals(123, uve.extract(null)); - - Map<String, ValueExtractor> values = filter.getValues(); - assertEquals(4, values.size()); - ValueExtractor extractor = values.get("header"); - assertTrue(extractor instanceof BeanValueExtractor); - BeanValueExtractor bve = (BeanValueExtractor) extractor; - assertEquals("last.req.getHeader('xyz')", bve.getFieldName()); - } - - private static void assertSeqItemValue(SequenceItem item, int num, long time, String alias) { - assertEquals("num", num, item.getNum()); - assertEquals("time", time, item.getTime()); - assertEquals("alias", alias, item.getAlias()); - } - - public static class TestHttpSequenceListener implements HttpSequenceListener { - - ArrayList<MapMetric> events = new ArrayList<>(); - - @Override - public void sequenceDetected(MapMetric sequence) { - events.add(sequence); - } - - public void reset() { - events.clear(); - } - } -}