/** * */ package com.thinkbiganalytics.metadata.jpa.sla; /*- * #%L * thinkbig-operational-metadata-jpa * %% * Copyright (C) 2017 ThinkBig Analytics * %% * 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. * #L% */ import com.thinkbiganalytics.metadata.api.MetadataAccess; import com.thinkbiganalytics.metadata.sla.api.AssessmentResult; import com.thinkbiganalytics.metadata.sla.api.Metric; import com.thinkbiganalytics.metadata.sla.api.MetricAssessment; import com.thinkbiganalytics.metadata.sla.api.Obligation; import com.thinkbiganalytics.metadata.sla.api.ObligationAssessment; import com.thinkbiganalytics.metadata.sla.api.ObligationGroup; import com.thinkbiganalytics.metadata.sla.api.ObligationGroup.Condition; import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAgreement; import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAgreement.ID; import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAssessment; import com.thinkbiganalytics.metadata.sla.spi.AssessorNotFoundException; import com.thinkbiganalytics.metadata.sla.spi.MetricAssessmentBuilder; import com.thinkbiganalytics.metadata.sla.spi.MetricAssessor; import com.thinkbiganalytics.metadata.sla.spi.ObligationAssessmentBuilder; import com.thinkbiganalytics.metadata.sla.spi.ObligationAssessor; import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAgreementProvider; import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAssessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.inject.Inject; /** * A service level agreement assessor that will assess service level agreements checking the obligations and metrics and validating if the agreement is violated or not */ public class JpaServiceLevelAssessor implements ServiceLevelAssessor { private static final Logger log = LoggerFactory.getLogger(JpaServiceLevelAssessor.class); @Inject MetadataAccess metadataAccess; @Inject private JpaServiceLevelAssessmentProvider assessmentProvider; @Inject private ServiceLevelAgreementProvider agreementProvider; private ObligationAssessor<? extends Obligation> defaultObligationAssessor; private Set<ObligationAssessor<? extends Obligation>> obligationAssessors; private Set<MetricAssessor<? extends Metric, ? extends Serializable>> metricAssessors; public JpaServiceLevelAssessor() { this.obligationAssessors = Collections.synchronizedSet(new HashSet<ObligationAssessor<? extends Obligation>>()); this.metricAssessors = Collections.synchronizedSet(new HashSet<MetricAssessor<? extends Metric, ? extends Serializable>>()); this.defaultObligationAssessor = new DefaultObligationAssessor(); } /* * (non-Javadoc) * * @see com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAssessor# * registerObligationAssessor(com.thinkbiganalytics.metadata.sla.spi. * ObligationAssessor) */ @Override public ObligationAssessor<? extends Obligation> registerObligationAssessor(ObligationAssessor<? extends Obligation> assessor) { this.obligationAssessors.add(assessor); return assessor; } /* * (non-Javadoc) * * @see com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAssessor# * registerMetricAssessor(com.thinkbiganalytics.metadata.sla.spi. * MetricAssessor) */ @Override public MetricAssessor<? extends Metric, ? extends Serializable> registerMetricAssessor(MetricAssessor<? extends Metric, ? extends Serializable> assessor) { this.metricAssessors.add(assessor); return assessor; } private List<Comparable<? extends Serializable>> sanitizeComparablesArray(Comparable<? extends Serializable>... comparables) { List<Comparable<? extends Serializable>> list = new ArrayList<>(); if (comparables != null) { for (Comparable<? extends Serializable> comparable : comparables) { list.add(sanitizeComparable(comparable)); } } return list; } private Comparable<? extends Serializable> sanitizeComparable(Comparable<? extends Serializable> comparable) { if (comparable != null && (comparable instanceof Date)) { return ((Date) comparable).getTime(); } else { return comparable; } } /** * Needs to be wrapped in metadataAccess.read */ public ServiceLevelAssessment assess(ID id) { ServiceLevelAgreement sla = agreementProvider.getAgreement(id); if (sla == null) { //TODO add explicit NotFoundException throw new RuntimeException("Failed to get sla node for " + id); } return assess(sla); } @Override public ServiceLevelAssessment findLatestAssessment(ServiceLevelAgreement sla) { return this.metadataAccess.read(() -> assessmentProvider.findLatestAssessment(sla.getId()), MetadataAccess.SERVICE); } /** * Assess the SLA (coming from JCR) * * @param sla the SLA to be assessed */ public ServiceLevelAssessment assess(ServiceLevelAgreement sla) { log.info("Assessing SLA: {}", sla.getName()); ServiceLevelAssessment assessment = null; ServiceLevelAgreement serviceLevelAgreement = sla; assessment = this.metadataAccess.commit(() -> { AssessmentResult combinedResult = AssessmentResult.FAILURE; try { //create the new Assessment JpaServiceLevelAssessment slaAssessment = new JpaServiceLevelAssessment(); slaAssessment.setId(JpaServiceLevelAssessment.SlaAssessmentId.create()); slaAssessment.setAgreement(serviceLevelAgreement); List<ObligationGroup> groups = sla.getObligationGroups(); for (ObligationGroup group : groups) { Condition condition = group.getCondition(); AssessmentResult groupResult = AssessmentResult.SUCCESS; Set<ObligationAssessment> obligationAssessments = new HashSet<>(); log.debug("Assessing obligation group {} with {} obligations", group, group.getObligations().size()); for (Obligation ob : group.getObligations()) { ObligationAssessment obAssessment = assess(ob, slaAssessment); obligationAssessments.add(obAssessment); // slaAssessment.add(obAssessment); groupResult = groupResult.max(obAssessment.getResult()); } slaAssessment.setObligationAssessments(obligationAssessments); // Short-circuit required or sufficient if necessary. switch (condition) { case REQUIRED: if (groupResult == AssessmentResult.FAILURE) { return completeAssessment(slaAssessment, groupResult); } break; case SUFFICIENT: if (groupResult != AssessmentResult.FAILURE) { return completeAssessment(slaAssessment, groupResult); } break; default: } // Required condition but non-failure, sufficient condition but non-success, or optional condition: // continue assessing groups and retain the best of the group results. combinedResult = combinedResult.min(groupResult); } return completeAssessment(slaAssessment, combinedResult); } finally { log.debug("Completed assessment of SLA {}: {}", sla.getName(), combinedResult); } }, MetadataAccess.SERVICE); return assessment; } private ObligationAssessment assess(Obligation ob, JpaServiceLevelAssessment serviceLevelAssessment) { ObligationAssessmentBuilderImpl builder = new ObligationAssessmentBuilderImpl(ob, serviceLevelAssessment); @SuppressWarnings("unchecked") ObligationAssessor<Obligation> assessor = (ObligationAssessor<Obligation>) findAssessor(ob); log.debug("Assessing obligation \"{}\" with assessor: {}", assessor); assessor.assess(ob, builder); return builder.build(); } private ServiceLevelAssessment completeAssessment(JpaServiceLevelAssessment slaAssessment, AssessmentResult result) { slaAssessment.setResult(result); String slaName = slaAssessment.getAgreement() != null ? slaAssessment.getAgreement().getName() : ""; if (result == AssessmentResult.SUCCESS) { slaAssessment.setMessage("SLA assessment requirements were met for '" + slaName + "'"); } else { slaAssessment.setMessage("At least one of the SLA obligations for '" + slaName + "' resulted in the status: " + result); } //save it assessmentProvider.save(slaAssessment); return slaAssessment; } protected ObligationAssessor<? extends Obligation> findAssessor(Obligation obligation) { synchronized (this.obligationAssessors) { for (ObligationAssessor<? extends Obligation> assessor : this.obligationAssessors) { if (assessor.accepts(obligation)) { return assessor; } } } return this.defaultObligationAssessor; } @SuppressWarnings("unchecked") protected <M extends Metric> MetricAssessor<M, ?> findAssessor(M metric) { synchronized (this.metricAssessors) { for (MetricAssessor<? extends Metric, ? extends Serializable> accessor : this.metricAssessors) { if (accessor.accepts(metric)) { return (MetricAssessor<M, ?>) accessor; } } } throw new AssessorNotFoundException(metric); } private class ObligationAssessmentBuilderImpl implements ObligationAssessmentBuilder { private Obligation obligation; private String message = ""; private AssessmentResult result = AssessmentResult.SUCCESS; private Comparator<ObligationAssessment> comparator; private List<Comparable<? extends Serializable>> comparables; private ServiceLevelAssessment serviceLevelAssessment; private JpaObligationAssessment assessment; public ObligationAssessmentBuilderImpl(Obligation obligation, JpaServiceLevelAssessment serviceLevelAssessment) { this.obligation = obligation; this.assessment = new JpaObligationAssessment(); this.assessment.setObligation(obligation); this.serviceLevelAssessment = serviceLevelAssessment; serviceLevelAssessment.getObligationAssessments().add(this.assessment); this.assessment.setServiceLevelAssessment(serviceLevelAssessment); } @Override public ObligationAssessmentBuilder obligation(Obligation ob) { this.obligation = ob; return this; } @Override public ObligationAssessmentBuilder result(AssessmentResult result) { this.result = result; return this; } @Override public ObligationAssessmentBuilder message(String descr) { this.message = descr; return this; } @Override public ObligationAssessmentBuilder comparator(Comparator<ObligationAssessment> comp) { this.comparator = comp; return this; } @Override @SuppressWarnings("unchecked") public ObligationAssessmentBuilder compareWith(final Comparable<? extends Serializable> value, @SuppressWarnings("unchecked") final Comparable<? extends Serializable>... otherValues) { this.comparables = new ArrayList<Comparable<? extends Serializable>>(Arrays.asList(sanitizeComparable(value))); this.comparables.addAll(sanitizeComparablesArray(otherValues)); return this; } @Override public <M extends Metric> MetricAssessment<?> assess(M metric) { MetricAssessor<M, ?> assessor = findAssessor(metric); MetricAssessmentBuilderImpl builder = new MetricAssessmentBuilderImpl(metric, this.assessment); assessor.assess(metric, builder); MetricAssessment<?> metricAssmt = builder.build(); return metricAssmt; } protected ObligationAssessment build() { this.assessment.setObligation(this.obligation); this.assessment.setMessage(this.message); this.assessment.setResult(this.result); if (this.comparator != null) { this.assessment.setComparator(this.comparator); } if (this.comparables != null) { this.assessment.setComparables(this.comparables); } this.serviceLevelAssessment.getObligationAssessments().add(this.assessment); return this.assessment; } } private class MetricAssessmentBuilderImpl<D extends Serializable> implements MetricAssessmentBuilder<D> { private Metric metric; private String message = ""; private AssessmentResult result = AssessmentResult.SUCCESS; private D data; private Comparator<MetricAssessment<D>> comparator; private List<Comparable<? extends Serializable>> comparables; private JpaObligationAssessment obligationAssessment; public MetricAssessmentBuilderImpl(Metric metric, JpaObligationAssessment obligationAssessment) { this.obligationAssessment = obligationAssessment; this.metric = metric; } /** * not used **/ public MetricAssessmentBuilderImpl(Metric metric) { this.metric = metric; } /** * not used **/ @Override public MetricAssessmentBuilder<D> metric(Metric metric) { this.metric = metric; return this; } @Override public MetricAssessmentBuilder<D> message(String descr) { this.message = descr; return this; } @Override public MetricAssessmentBuilder<D> result(AssessmentResult result) { this.result = result; return this; } @Override public MetricAssessmentBuilder<D> data(D data) { this.data = data; return this; } @Override public MetricAssessmentBuilder<D> comparitor(Comparator<MetricAssessment<D>> comp) { this.comparator = comp; return this; } @Override @SuppressWarnings("unchecked") public MetricAssessmentBuilder<D> compareWith(Comparable<? extends Serializable> value, Comparable<? extends Serializable>... otherValues) { this.comparables = new ArrayList<Comparable<? extends Serializable>>(Arrays.asList(sanitizeComparable(value))); this.comparables.addAll(sanitizeComparablesArray(otherValues)); return this; } protected MetricAssessment build() { JpaMetricAssessment<D> assessment = new JpaMetricAssessment<>(); assessment.setMetric(this.metric); assessment.setMessage(this.message); assessment.setResult(this.result); assessment.setMetricDescription(this.metric.getDescription()); //assessment.setData(this.data); if (this.comparator != null) { assessment.setComparator(this.comparator); } if (this.comparables != null) { assessment.setComparables(this.comparables); } obligationAssessment.getMetricAssessments().add(assessment); assessment.setObligationAssessment(obligationAssessment); return assessment; } } protected class DefaultObligationAssessor implements ObligationAssessor<Obligation> { @Override public boolean accepts(Obligation obligation) { // Accepts any obligations return true; } @Override public void assess(Obligation obligation, ObligationAssessmentBuilder builder) { log.debug("Assessing obligation '{}' with {} metrics", obligation, obligation.getMetrics().size()); Set<MetricAssessment> metricAssessments = new HashSet<MetricAssessment>(); ObligationAssessmentBuilderImpl obligationAssessmentBuilder = (ObligationAssessmentBuilderImpl) builder; AssessmentResult result = AssessmentResult.SUCCESS; for (Metric metric : obligation.getMetrics()) { log.debug("Assessing obligation metric '{}'", metric); MetricAssessment assessment = obligationAssessmentBuilder.assess(metric); metricAssessments.add(assessment); } for (MetricAssessment ma : metricAssessments) { result = result.max(ma.getResult()); } String message = "The obligation requirements were met"; if (result != AssessmentResult.SUCCESS) { message = "At least one metric assessment resulted in the status: " + result; } log.debug("Assessment outcome '{}'", message); builder .result(result) .message(message) .obligation(obligation); } } }