/******************************************************************************* * Copyright (c) 2014 Imperial College London * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Raul Castro Fernandez - initial API and implementation ******************************************************************************/ package uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.evaluate; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.joda.time.Instant; import org.joda.time.Period; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.PolicyRule; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.operator.OneOperator; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.scale.constraint.RelativeScaleConstraint; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.scale.constraint.ScaleConstraint; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.trigger.ActionTrigger; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.util.InfrastructureAdaptor; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.util.MetricReading; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.util.MetricReadingProvider; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.util.TimeReference; /** * Evaluator for a single policy rule. The class relies on an infrastructure adaptor * to inform the platform the new size of a given operator and query it to determine * its current size, when needed. Using an adaptor instigates decoupling from the * rest of the platform and facilitates testing. * * @author mrouaux */ public class PolicyRuleEvaluator extends AbstractEvaluator<PolicyRule, InfrastructureAdaptor, MetricReadingProvider>{ private static final Logger logger = LoggerFactory.getLogger(PolicyRuleEvaluator.class); private Queue<MetricReading> pastReadings; private Period maximumAge; private TimeReference clock; /** * Convenience constructor * @param rules scaling policy rules to evaluate * @param adaptor infrastructure adaptor (allows evaluators to delegate resizing * of a particular operator as a consequence of an action triggered by some * of the rules being evaluated). An adaptor is used to decouple policy * evaluation from execution. */ public PolicyRuleEvaluator(final PolicyRule rule, final InfrastructureAdaptor adaptor, final TimeReference clock) { super(rule, adaptor); // Some rules need extra runtime configuration before being evaluated initRule(rule, adaptor); this.clock = clock; // Initialise the evaluator pastReadings = new LinkedList<MetricReading>(); calcMaxAgeForReadings(); } /** * * @param provider */ @Override public void evaluate(MetricReadingProvider provider) { InfrastructureAdaptor adaptor = getEvalAdaptor(); PolicyRule rule = getEvalSubject(); MetricReading reading = provider.nextReading(); if(reading != null) { pastReadings.offer(reading); } // Now prune old readings that are no longer needed given the time // threshold for the current rule. There might be multiple time thresholds // (as we support multiple triggers per rule). We need to keep enough // readings for the one with the longest time horizon pruneOldReadings(maximumAge); // Evaluate all the triggers for the rule, when all are fired and have // changed, we need to execute the associated scaling action boolean scale = true; // Determine if we need to scale, the algorithm has to check all the readings // Delegate to triggers as they implement the logic to determine whether to // scale or not. All triggers need to be evaluated. List<ActionTrigger> triggers = rule.getTriggers(); if((triggers == null) || (triggers.isEmpty())) { scale = false; } else { // Evaluate each one of the triggers for(ActionTrigger trigger : triggers) { trigger.evaluate(new ArrayList<MetricReading>(pastReadings), clock); scale = (scale && (trigger.isFired() && trigger.hasChanged())); if(!scale) { // If one trigger evaluates to false, we are done. No need to // continue checking the rest as the rule will not scale. break; } } } // Execute the scale action if(scale) { if(rule.getOperator() instanceof OneOperator) { OneOperator op = (OneOperator)rule.getOperator(); int operatorId = op.getId(); // Scaling is applied to a single operator in the query scaleForSingleOperator(operatorId, rule, adaptor); } else { // Operator is of type AllOperators, scaling needs to be applied // to all the operators in the query. scaleForAllOperators(rule, adaptor); } } } /** * Scale a single operator identified by operatorId. * * @param operatorId Operator identifier * @param rule Rule that governs the scaling (this method assumes that all * riggers associated to the rule have already been evaluated and they have * all fired accordingly). * @param adaptor Infrastructure adaptor */ private void scaleForSingleOperator(final int operatorId, final PolicyRule rule, final InfrastructureAdaptor adaptor) { // Calculate new size for operator by applying scale factor int currenSize = adaptor.getOperatorCurrentSize(operatorId); int scaledSize = rule.getScaleFactor() .apply(currenSize, rule.getAction()); boolean constraintExceeded = false; ScaleConstraint constraint = rule.getScaleConstraint(); if(constraint != null) { constraintExceeded = constraint.evaluate(scaledSize); } if(!constraintExceeded) { adaptor.setOperatorScaledSize(operatorId, scaledSize); } } /** * Scale for all operators in the current query. * * @param rule Rule that governs the scaling (this method assumes that all * riggers associated to the rule have already been evaluated and they have * all fired accordingly). * @param adaptor Infrastructure adaptor */ private void scaleForAllOperators(final PolicyRule rule, final InfrastructureAdaptor adaptor) { List<Integer> operatorIds = adaptor.getOperatorIds(); if(operatorIds != null) { for(Integer operatorId : operatorIds) { scaleForSingleOperator(operatorId, rule, adaptor); } } } /** * Prune old readings from the queue of readings required to evaluate all the * triggers associated to the scaling rule bound to this evaluator. * * @param maximumAge Maximum age for metric readings in the queue. Any readings * which have a timestamp older than this age can be safely discarded as they * are no longer needed to evaluate the state of the rule triggers. */ private void pruneOldReadings(Period maximumAge) { boolean done = false; MetricReading reading = pastReadings.peek(); while((reading != null) && !done) { Period readingAge = new Period( reading.getTimestamp(), Instant.now()); // Once we find a reading with an age less than the maximum age, we // stop prunning as readings in the queue are sorted. An evaluator // works based on the assumption that metrics readings are offered // with always increasing timestamps. if(readingAge.toStandardSeconds() .isLessThan(maximumAge.toStandardSeconds())) { done = true; } else { pastReadings.poll(); // Get the next element in the queue and continue pruning reading = pastReadings.peek(); } } } /** * Given all the triggers associated to the scaling rule, it finds the one * with the longest time threshold (longest horizon). The evaluator needs to * be aware of this as this is the cut-off for metric readings, any readings * older than the maximum time threshold can safely be discarded. */ private void calcMaxAgeForReadings() { PolicyRule rule = getEvalSubject(); maximumAge = null; for(ActionTrigger trigger : rule.getTriggers()) { Period triggerAge = trigger.getTimeThreshold().toPeriod(); if(maximumAge == null) { maximumAge = triggerAge; } else { if(triggerAge.toStandardSeconds() .isGreaterThan(maximumAge.toStandardSeconds())) { maximumAge = triggerAge; } } } } /** * Initializes a policy rule. * * @param rule * @param adaptor */ private void initRule(PolicyRule rule, InfrastructureAdaptor adaptor) { if(rule.getScaleConstraint() instanceof RelativeScaleConstraint) { if(rule.getOperator() instanceof OneOperator) { OneOperator op = (OneOperator)rule.getOperator(); ((RelativeScaleConstraint)rule.getScaleConstraint()) .withOriginalSize( adaptor.getOperatorOriginalSize(op.getId())); } } } @Override public String toString() { PolicyRule rule = getEvalSubject(); return "PolicyRuleEvaluator{" + rule.toString() + '}'; } }