/*
* Copyright 2013 Cloud4SOA, www.cloud4soa.eu
*
* Licensed 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 eu.cloud4soa.governance.sla.enforcement.task;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.cloud4soa.api.datamodel.governance.SlaTemplate;
import eu.cloud4soa.api.datamodel.governance.SlaTemplate.ServiceGuaranteeType;
import eu.cloud4soa.api.governance.monitoring.IMonitoringMetric;
import eu.cloud4soa.api.governance.monitoring.IMonitoringMetric.MetricKey;
import eu.cloud4soa.governance.monitoring.MonitoringModule;
import eu.cloud4soa.governance.sla.decisor.SLADecisor;
import eu.cloud4soa.relational.datamodel.ApplicationInstance;
import eu.cloud4soa.relational.datamodel.Breach;
import eu.cloud4soa.relational.datamodel.GuaranteeTerm;
import eu.cloud4soa.relational.datamodel.ISLAEnforcementJob;
import eu.cloud4soa.relational.datamodel.SLAContract;
import eu.cloud4soa.relational.datamodel.SLAPolicy;
import eu.cloud4soa.relational.datamodel.SLAViolation;
import eu.cloud4soa.relational.persistence.ApplicationInstanceRepository;
import eu.cloud4soa.relational.persistence.BreachRepository;
import eu.cloud4soa.relational.persistence.GuaranteeTermRepository;
import eu.cloud4soa.relational.persistence.RecoveryActionRepository;
import eu.cloud4soa.relational.persistence.SLAContractRepository;
import eu.cloud4soa.relational.persistence.SLAPolicyRepository;
import eu.cloud4soa.relational.persistence.SLAViolationRepository;
public class SLAEnforcementTask extends TimerTask implements Runnable{
/*
* This is a low value, but if metrics are taken each 30s, it will take a long to have
* 30 samples, for example.
*/
private static final int MIN_UPTIME_SAMPLES = 10;
final Logger logger = LoggerFactory.getLogger(getClass());
private BreachRepository breach_repository;
private SLAViolationRepository slaViolationRepository;
private RecoveryActionRepository recoveryActionRepository;
private ApplicationInstanceRepository applicationInstanceRepository;
private SLAContractRepository slaContractRepository;
private GuaranteeTermRepository guaranteeTermRepository;
private SLAPolicyRepository slaPolicyRepository;
private ISLAEnforcementJob slaEnforcementJob;
private MonitoringModule monitoringModule;
private SLAContract slaContract;
private Date lastExecuted;
private Date called;
private HashMap<ServiceGuaranteeType, ArrayList <Long>> violations;
/*TODO Temporal solution until agreement of what type to use*/
/* The good */
private ApplicationInstance persistedApplicationInstance;
/* The bad and the ugly */
private eu.cloud4soa.api.datamodel.core.ApplicationInstance apiApplicationInstance;
/*
* applicationInstance = this.applicationProfilesRepository.getApplicationInstance(applicationUriId);
* */
public SLAEnforcementTask(SLAViolationRepository slaViolationRepository,
RecoveryActionRepository recoveryActionRepository,
ApplicationInstanceRepository applicationInstanceRepository,
SLAContractRepository slaContractRepository,
GuaranteeTermRepository guaranteeTermRepository,
ISLAEnforcementJob slaEnforcementJob,
MonitoringModule monitoringModule,
Date lastExecuted,
BreachRepository breachRepository,
SLAPolicyRepository slaPolicyRepository){
this.recoveryActionRepository = recoveryActionRepository;
this.slaEnforcementJob = slaEnforcementJob;
this.slaViolationRepository = slaViolationRepository;
this.applicationInstanceRepository = applicationInstanceRepository;
this.slaContractRepository = slaContractRepository;
this.guaranteeTermRepository = guaranteeTermRepository;
this.monitoringModule = monitoringModule;
this.breach_repository = breachRepository;
this.violations =
new HashMap<ServiceGuaranteeType, ArrayList<Long>>();
/*TODO There could be more than one application instance per uri id?*/
this.persistedApplicationInstance = applicationInstanceRepository.findByUriIdNoCheck(slaEnforcementJob.getApplicationInstanceUriId()).get(0);
this.lastExecuted = lastExecuted;
/*TODO Temporal solution until agreement of what type to use*/
apiApplicationInstance = toApiApplicationInstance(persistedApplicationInstance);
}
@Override
public void run() {
called = new Date();
slaContract = slaContractRepository.retrieveAll(Long.parseLong(slaEnforcementJob.getSlaContractId())).get(0);
checkViolationsInPeriod(persistedApplicationInstance, slaContract, called);
}
private SLAViolation createandStoreSLAViolation(String metricName,
SLAPolicy slaPolicy,
float expectedValue,
float actualValue,
List <Breach> breaches) {
SLAViolation violation = new SLAViolation(persistedApplicationInstance.getUriID(),
metricName,
expectedValue,
actualValue,
breaches);
violation.setDateAndTime(called);
violation.setSLAEnforcementJobId(slaEnforcementJob.getId());
if (slaPolicy != null) {
violation.setSlaPolicyId(slaPolicy.getId());
}
slaViolationRepository.store(violation);
//TODO: switch print out with logger after development
//logger.debug("SLAEnforcementTask found: " + violation);
System.out.println("SLAEnforcementTask found: " + violation);
return violation;
}
private void logFindings (String metricName,int expectedValue, float actualValue) {
//TODO: switch print out with logger after development
//logger.debug("SLAEnforcementTask stats for" + metricName + ": Expected " + expectedValue + " percent compliance, found " + actualValue + " percent compliance");
System.out.println("SLAEnforcementTask stats for" + metricName +
": Expected " + expectedValue +
" percent compliance, found " + actualValue +
" percent compliance");
}
private void checkViolationsInPeriod (ApplicationInstance applicationInstance,
SLAContract contract,
Date end) {
Date checkMetricsBeginning, checkBreachesBeginning;
String applicationUriID = applicationInstance.getUriID();
List<SLAPolicy> policies = contract.getSlaPolicies();
List<Breach> breaches;
List<IMonitoringMetric> metrics;
Date now = new Date();
int nBreaches;
Breach newBreach;
SLADecisor decisor = new SLADecisor
(recoveryActionRepository,
slaViolationRepository,
contract,
applicationInstance,
slaPolicyRepository);
SLAViolation violation;
logger.debug("checkViolationsInPeriod in ");
for (GuaranteeTerm guaranteeTerm : contract.getGuaranteeTerms()) {
if (guaranteeTerm == null ||
guaranteeTerm.getCustomServiceLevel() == null) {
continue;
}
double threshold = Double.parseDouble(guaranteeTerm.getCustomServiceLevel());
String kpiName = guaranteeTerm.getKpiName();
if (policies.isEmpty()) {
logger.debug("No policies - About to read metrics");
metrics =
monitoringModule.getMonitoringMetricsWhithinRangeLimited
(applicationUriID, IMonitoringMetric.MetricKey.valueOf(kpiName),
lastExecuted, now, 1000);
Iterator<IMonitoringMetric> it = getBreachesInMetrics(kpiName, metrics, threshold);
while (it.hasNext()) {
IMonitoringMetric metric = it.next();
if (metric == null) {
continue;
}
double metricValue = metric.getMetricValue();
/*
* W/o policies, consider each breach a violation
*/
createandStoreSLAViolation(kpiName,
null, Float.parseFloat(guaranteeTerm.getCustomServiceLevel()),
(float)metricValue, null);
}
} else {
for (SLAPolicy policy : contract.getSlaPolicies()) {
ServiceGuaranteeType metric_name = policy.getMetric_name();
if (kpiName.equals(metric_name.toString())){
long policyInterval = policy.getTime_interval().getTime();
checkBreachesBeginning = new Date(now.getTime() - policyInterval);
logger.debug("With policies - About to read metrics");
/*
* TODO: checkBreachesBeginning shoud be max(cBB, last(violation)).
* Ioc, we could be generating the same violations again and again until
* the breaches leave the time window.
*/
breaches =
breach_repository.retrieveAllInRangeLimited
(applicationUriID, policy.getId(),
kpiName,
checkBreachesBeginning, end);
nBreaches = breaches.size();
/*
* checkMetricsBeginning is max(now - interval, last(breach)).
* Ioc, we could be generating the same breaches again and again until
* the metrics leave the time window.
*/
if (nBreaches > 0) {
checkMetricsBeginning = breaches.get(0).getTimestamp();
}
else {
checkMetricsBeginning = new Date(now.getTime() - policyInterval);
}
metrics =
monitoringModule.getMonitoringMetricsWhithinRangeLimited
(applicationUriID, IMonitoringMetric.MetricKey.valueOf(kpiName),
checkMetricsBeginning, now, 1000);
Iterator<IMonitoringMetric> it = getBreachesInMetrics(kpiName, metrics, threshold);
while (it.hasNext()) {
IMonitoringMetric metric = it.next();
if (metric == null) {
continue;
}
double metricValue = metric.getMetricValue();
newBreach = storeBreach(metric.getDate(),
policy.getId(),
applicationUriID,
null,
metric_name.toString(),
String.valueOf(metricValue));
breaches.add(newBreach);
if (++nBreaches > policy.getBreach()) {
violation = createandStoreSLAViolation
(kpiName, policy,
Float.parseFloat(guaranteeTerm.getCustomServiceLevel()),
(float)metricValue,
breaches);
/*
* TODO: slaPolicyRepository is null because is not constructed
* in spring yet. Uncomment this when solved.
*/
// decisor.createRecoveryAction(contract, violation);
/*
* We only store one violation per task execution.
*/
break;
}
}
}
}
}
}
logger.debug("checkViolationsInPeriod out");
}
private Breach storeBreach (Date date,
Long slaPolicy,
String applicationInstanceUriId,
String user_id,
String type,
String value) {
Breach breach = new Breach();
breach.setTimestamp(date);
breach.setSlaPolicyId(slaPolicy);
breach.setApplicationInstanceUriId(applicationInstanceUriId);
breach.setUser_id(user_id);
breach.setType(type);
breach.setValue(value);
breach_repository.store(breach);
return breach;
}
/*TODO Temporal solution until agreement of what type to use*/
private eu.cloud4soa.api.datamodel.core.ApplicationInstance toApiApplicationInstance (ApplicationInstance appInstance) {
eu.cloud4soa.api.datamodel.core.ApplicationInstance ret =
new eu.cloud4soa.api.datamodel.core.ApplicationInstance();
ret.setAdapterUrl(appInstance.getAdapterurl());
ret.setApplicationDeploymentUriId(appInstance.getAppurl());
ret.setUriId(appInstance.getUriID());
ret.setVersion(appInstance.getVersion());
ret.setOwnerUriId(appInstance.getAccount().getUser().getUriID());
return ret;
}
/**
* Iterator over the uptime breaches in a list of metrics.
*
* A list of metrics causes an uptime breach if percentage of values=200 if less than
* <code>minUptime</code>. There MUST be more or equal than <code>minSamples</code> samples.
*
* To ease implementation, this iterator can return <code>null</code>,
* so nulls have to be skipped in client call.
*/
private class UptimeCheckerIterator implements Iterator<IMonitoringMetric> {
private List<IMonitoringMetric> metrics;
private ListIterator<IMonitoringMetric> it;
private double minUptime;
private int minSamples;
public UptimeCheckerIterator(List<IMonitoringMetric> metrics,
double minUptime, int minSamples) {
this.metrics = metrics;
this.it = metrics.listIterator();
this.minUptime = minUptime;
this.minSamples = minSamples;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public IMonitoringMetric next() {
int isUp = 0;
int size = 0;
if (!it.hasNext()) {
throw new NoSuchElementException();
}
IMonitoringMetric metric = null;
while (it.hasNext()) {
metric = it.next();
if (metric.getMetricValue() == 200) {
isUp++;
}
size++;
}
final double uptime = isUp * 100.0 / size;
if (size >= minSamples && uptime < minUptime) {
/*
* Return onthefly IMonitoringMetric. Get date of last metric as metric date
* (metrics are date desc ordered)
*/
metric = metrics.get(0);
final MetricKey metricKey = metric.getMetricKey();
final Date metricDate = metric.getDate();
IMonitoringMetric result = new IMonitoringMetric() {
@Override
public MetricKey getMetricKey() {
return metricKey;
}
@Override
public double getMetricValue() {
return uptime;
}
@Override
public Date getDate() {
return metricDate;
}
};
return result;
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException("CheckerIterator does not support remove");
}
}
/**
* Iterator over the time breaches in a list of metrics.
*
* A metric is a breach if its value is less than <code>maxTime</code>
*
* To ease implementation, this iterator return <code>null</code> on any non-breach metric,
* so nulls have to be skipped in client call.
*/
private class TimeCheckerIterator implements Iterator<IMonitoringMetric> {
private List<IMonitoringMetric> metrics;
private ListIterator<IMonitoringMetric> it;
private double maxTime;
public TimeCheckerIterator(List<IMonitoringMetric> metrics, double maxTime) {
this.metrics = metrics;
this.it = metrics.listIterator();
this.maxTime = maxTime;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public IMonitoringMetric next() {
IMonitoringMetric metric = it.next();
if (metric.getMetricValue() > maxTime) {
return metric;
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException("CheckerIterator does not support remove");
}
}
/**
* Simple factory method to get checker given a metric name.
*/
private Iterator<IMonitoringMetric> getBreachesInMetrics(String metricName,
List<IMonitoringMetric> metrics, double threshold) {
/*
* TODO: cpu_load and memory_load are returned as TimeCheckerIterator, and that's wrong.
* CapacityCheckerIterator should be implemented if these parameters are needed.
*/
if (IMonitoringMetric.MetricKey.statusCode.toString().equals(metricName)) {
return new UptimeCheckerIterator(metrics, threshold, MIN_UPTIME_SAMPLES);
}
return new TimeCheckerIterator(metrics, threshold);
}
}