package com.intrbiz.bergamot.model;
import java.util.EnumSet;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import com.intrbiz.Util;
import com.intrbiz.bergamot.config.model.ActiveCheckCfg;
import com.intrbiz.bergamot.data.BergamotDB;
import com.intrbiz.bergamot.express.BergamotEntityResolver;
import com.intrbiz.bergamot.io.BergamotTranscoder;
import com.intrbiz.bergamot.model.message.ActiveCheckMO;
import com.intrbiz.bergamot.model.message.check.ExecuteCheck;
import com.intrbiz.bergamot.model.state.CheckSavedState;
import com.intrbiz.bergamot.model.state.CheckState;
import com.intrbiz.bergamot.model.util.Parameter;
import com.intrbiz.bergamot.queue.key.WorkerKey;
import com.intrbiz.bergamot.util.TimeInterval;
import com.intrbiz.data.db.compiler.meta.Action;
import com.intrbiz.data.db.compiler.meta.SQLColumn;
import com.intrbiz.data.db.compiler.meta.SQLForeignKey;
import com.intrbiz.data.db.compiler.meta.SQLVersion;
import com.intrbiz.express.DefaultContext;
import com.intrbiz.express.ExpressContext;
import com.intrbiz.express.value.ValueExpression;
/**
* A check which is actively polled
*/
public abstract class ActiveCheck<T extends ActiveCheckMO, C extends ActiveCheckCfg<C>> extends RealCheck<T, C>
{
private static final long serialVersionUID = 1L;
/**
* How often should checks be executed (in milliseconds)
*/
@SQLColumn(index = 1, name = "check_interval", since = @SQLVersion({ 1, 0, 0 }))
protected long checkInterval = TimeUnit.MINUTES.toMillis(5);
/**
* How often should checks be executed when not in an ok state (in milliseconds)
*/
@SQLColumn(index = 2, name = "retry_interval", since = @SQLVersion({ 1, 0, 0 }))
protected long retryInterval = TimeUnit.MINUTES.toMillis(1);
/**
* When should we check, a calendar
*/
@SQLColumn(index = 3, name = "timeperiod_id", since = @SQLVersion({ 1, 0, 0 }))
@SQLForeignKey(references = TimePeriod.class, on = "id", onDelete = Action.RESTRICT, onUpdate = Action.RESTRICT, since = @SQLVersion({ 1, 0, 0 }))
protected UUID timePeriodId;
@SQLColumn(index = 4, name = "worker_pool", since = @SQLVersion({ 1, 0, 0 }))
protected String workerPool;
/**
* How often should checks be executed when transitioning between hard states (in milliseconds)
*/
@SQLColumn(index = 5, name = "changing_interval", since = @SQLVersion({ 1, 4, 0 }))
protected long changingInterval = 0;
public ActiveCheck()
{
super();
}
public long getCheckInterval()
{
return checkInterval;
}
public void setCheckInterval(long checkInterval)
{
this.checkInterval = checkInterval;
}
public long getRetryInterval()
{
return retryInterval;
}
public void setRetryInterval(long retryInterval)
{
this.retryInterval = retryInterval;
}
public long getChangingInterval()
{
return changingInterval;
}
public void setChangingInterval(long changingInterval)
{
this.changingInterval = changingInterval;
}
/**
* Get the current check interval for this check, given it's current state
*/
public long getCurrentInterval()
{
return this.computeCurrentInterval(this.getState());
}
/**
* Compute the current interval for this check using the
* check state and the configured intervals.
*/
public long computeCurrentInterval(CheckState state)
{
if (state.isSoft() && this.getChangingInterval() > 0)
{
return this.getChangingInterval();
}
else if (state.isHardOk())
{
return this.getCheckInterval();
}
else
{
return this.getRetryInterval();
}
}
public TimePeriod getTimePeriod()
{
if (this.getTimePeriodId() == null) return null;
try (BergamotDB db = BergamotDB.connect())
{
return db.getTimePeriod(this.getTimePeriodId());
}
}
public UUID getTimePeriodId()
{
return timePeriodId;
}
public void setTimePeriodId(UUID timePeriodId)
{
this.timePeriodId = timePeriodId;
}
public String getWorkerPool()
{
return workerPool;
}
public void setWorkerPool(String workerPool)
{
this.workerPool = workerPool;
}
public abstract String resolveWorkerPool();
public abstract UUID resolveAgentId();
/**
* Get the check saved state if any exists
* @return the saved state or null
*/
public CheckSavedState getSavedState()
{
try (BergamotDB db = BergamotDB.connect())
{
return db.getCheckSavedState(this.getId());
}
}
/**
* Construct the routing key which should be used to route this execute check message
*
* @return
*/
public WorkerKey getRoutingKey()
{
Command command = Util.nullable(this.getCheckCommand(), CheckCommand::getCommand);
if (command == null) return null;
// the key
return new WorkerKey(this.getSiteId(), this.resolveWorkerPool(), command.getEngine(), this.resolveAgentId());
}
/**
* Get the TTL in ms for the execute check message
*
* @return
*/
public long getMessageTTL()
{
return this.getCurrentInterval();
}
/**
* Construct the execute check message for this check
*/
public final ExecuteCheck executeCheck()
{
Logger logger = Logger.getLogger(ActiveCheck.class);
CheckCommand checkCommand = this.getCheckCommand();
if (checkCommand == null) return null;
Command command = checkCommand.getCommand();
if (command == null) return null;
if (logger.isTraceEnabled()) logger.trace("Executing check of " + this.getType() + "::" + this.getId() + " " + this.getName() + " with command " + command);
ExecuteCheck executeCheck = new ExecuteCheck();
executeCheck.setId(UUID.randomUUID());
executeCheck.setSiteId(this.getSiteId());
executeCheck.setAgentId(this.resolveAgentId());
executeCheck.setCheckType(this.getType());
executeCheck.setCheckId(this.getId());
executeCheck.setProcessingPool(this.getPool());
executeCheck.setEngine(command.getEngine());
executeCheck.setExecutor(command.getExecutor());
executeCheck.setName(command.getName());
executeCheck.setScript(checkCommand.resolveCheckScript());
// saved state?
CheckSavedState state = this.getSavedState();
if (state != null) executeCheck.setSavedState(state.getSavedState());
// eval parameters
ExpressContext context = new DefaultContext(new BergamotEntityResolver());
// configured parameters
for (Entry<String, Parameter> parameter : checkCommand.resolveCheckParameters().entrySet())
{
try
{
ValueExpression vexp = new ValueExpression(context, parameter.getValue().getValue());
String value = Util.nullable(vexp.get(context, this), Object::toString);
if (logger.isTraceEnabled()) logger.trace("Adding parameter: " + parameter.getKey() + " => " + value + " (" + parameter.getValue().getValue() + ")");
if (! Util.isEmpty(value)) executeCheck.setParameter(parameter.getKey(), value);
}
catch (Exception e)
{
logger.error("Error computing parameter value, for check: " + this.getType() + "::" + this.getId() + " " + this.getName() , e);
}
}
executeCheck.setTimeout(30_000L);
executeCheck.setScheduled(System.currentTimeMillis());
if (logger.isTraceEnabled())logger.trace("Executing check: " + new BergamotTranscoder().encodeAsString(executeCheck));
return executeCheck;
}
protected void toMO(ActiveCheckMO mo, Contact contact, EnumSet<MOFlag> options)
{
super.toMO(mo, contact, options);
mo.setCheckInterval(this.getCheckInterval());
mo.setRetryInterval(this.getRetryInterval());
mo.setCurrentInterval(this.getCurrentInterval());
TimePeriod timePeriod = this.getTimePeriod();
if (timePeriod != null)
{
if (contact == null || contact.hasPermission("read", timePeriod)) mo.setTimePeriod(timePeriod.toStubMO(contact));
}
}
public void configure(C configuration, C resolvedConfiguration)
{
super.configure(configuration, resolvedConfiguration);
// scheduling
if (resolvedConfiguration.getSchedule() != null)
{
this.checkInterval = resolvedConfiguration.getSchedule().getEveryTimeInterval(TimeInterval.minutes(5)).toMillis();
this.retryInterval = resolvedConfiguration.getSchedule().getRetryEveryTimeInterval(TimeInterval.minutes(1)).toMillis();
this.changingInterval = resolvedConfiguration.getSchedule().getChangingEveryTimeInterval(resolvedConfiguration.getSchedule().getRetryEveryTimeInterval(TimeInterval.minutes(1))).toMillis();
}
}
}