"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SLOAlarms = void 0;
const aws_cdk_lib_1 = require("aws-cdk-lib");
const constructs_1 = require("constructs");
const api_availability_metric_1 = require("./api-availability-metric");
const api_latency_metric_1 = require("./api-latency-metric");
const appsync_availability_metric_1 = require("./appsync-availability-metric");
const appsync_latency_metric_1 = require("./appsync-latency-metric");
const cloudfront_availability_metric_1 = require("./cloudfront-availability-metric");
const cloudfront_latency_metric_1 = require("./cloudfront-latency-metric");
const custom_availability_metric_1 = require("./custom-availability-metric");
const custom_latency_metric_1 = require("./custom-latency-metric");
const elasticsearch_availability_metric_1 = require("./elasticsearch-availability-metric");
const elasticsearch_latency_metric_1 = require("./elasticsearch-latency-metric");
const windows_1 = require("./windows");
/**
 * Creates multi-window alarms using CloudWatch composite alarms from a list of slo objects.
 * Also creates an SNS topic as an action for the parent alarm.
 */
class SLOAlarms extends constructs_1.Construct {
    constructor(scope, id, props) {
        super(scope, id);
        const windows = windows_1.Windows.standardAlarmMultiWindows;
        this.alarms = [];
        let alarmDescription = 'Service is at risk of exceeding its error budget. '
            + 'Please use the links below to troubleshoot the problem:'
            + `\n\nDashboard: ${props.dashboardLink}\nRun book: ${props.runbookLink}\n`;
        if (props.alarmsDashboardLink) {
            alarmDescription = `${alarmDescription}Alarms Dashboard: ${props.alarmsDashboardLink}\n`;
        }
        // Construct an SNS Topic for each unique severity type in the windows
        this.topics = windows.reduce((previous, current) => {
            if (previous[current.severity] === undefined) {
                previous[current.severity] = new aws_cdk_lib_1.aws_sns.Topic(scope, `${current.severity}SeverityTopic`);
            }
            return previous;
        }, {});
        // Create multi-window, multi-burn rate alarms for each SLO based on its type
        props.slos.forEach(slo => {
            let parentName;
            let alarmFactory;
            switch (slo.type) {
                case 'ApiAvailability':
                    parentName = `${slo.title} Availability <= ${slo.sloThreshold}`;
                    alarmFactory = SLOAlarms.apiAvailabilityAlarm;
                    break;
                case 'ApiLatency':
                    const apiLatencySLO = slo;
                    parentName = `${slo.title} Latency P${slo.sloThreshold * 100} >= ${apiLatencySLO.latencyThreshold}ms`;
                    alarmFactory = SLOAlarms.apiLatencyAlarm;
                    break;
                case 'AppSyncAvailability':
                    parentName = `${slo.title} Availability <= ${slo.sloThreshold}`;
                    alarmFactory = SLOAlarms.appSyncAvailabilityAlarm;
                    break;
                case 'AppSyncLatency':
                    const appSyncLatencySLO = slo;
                    parentName = `${slo.title} Latency P${slo.sloThreshold * 100} >= ${appSyncLatencySLO.latencyThreshold}ms`;
                    alarmFactory = SLOAlarms.appSyncLatencyAlarm;
                    break;
                case 'CloudfrontAvailability':
                    parentName = `${slo.title} Availability <= ${slo.sloThreshold}`;
                    alarmFactory = SLOAlarms.cloudfrontAvailabilityAlarm;
                    break;
                case 'CloudfrontLatency':
                    const cloudfrontLatencySLO = slo;
                    parentName = `${slo.title} Latency P${slo.sloThreshold * 100} >= ${cloudfrontLatencySLO.latencyThreshold}ms`;
                    alarmFactory = SLOAlarms.cloudfrontLatencyAlarm;
                    break;
                case 'ElasticSearchAvailability':
                    parentName = `${slo.title} Availability <= ${slo.sloThreshold}`;
                    alarmFactory = SLOAlarms.elasticSearchAvailabilityAlarm;
                    break;
                case 'ElasticSearchLatency':
                    const esLatencySLO = slo;
                    parentName = `${slo.title} Latency P${slo.sloThreshold * 100} >= ${esLatencySLO.latencyThreshold}ms`;
                    alarmFactory = SLOAlarms.elasticSearchLatencyAlarm;
                    break;
                case 'CustomAvailability':
                    parentName = `${slo.title} Availability <= ${slo.sloThreshold}`;
                    alarmFactory = SLOAlarms.customAvailabilityAlarm;
                    break;
                case 'CustomLatency':
                    const customLatencySLO = slo;
                    parentName = `${slo.title} Latency P${slo.sloThreshold * 100} >= ${customLatencySLO.latencyThreshold}ms`;
                    alarmFactory = SLOAlarms.customLatencyAlarm;
                    break;
                default:
                    throw new Error(`Alarms creation encountered an unknown type for slo: ${JSON.stringify(slo)}.`);
            }
            this.alarms.push(...SLOAlarms.createAlarms(windows, this, alarmDescription, this.topics, slo, parentName, alarmFactory));
        });
    }
}
exports.SLOAlarms = SLOAlarms;
/**
 * Factory method for constructing an Alarm based on an ApiAvailabilitySLO
 */
SLOAlarms.apiAvailabilityAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new api_availability_metric_1.ApiAvailabilityMetric({
        ...typedSlo,
        sloWindow,
    });
    const sloBudget = 1 - typedSlo.sloThreshold;
    let alarmThreshold = 1 - sloWindow.burnRateThreshold * sloBudget;
    alarmThreshold = Math.max(0, Math.min(alarmThreshold, 1));
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: alarmThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an ApiLatencySLO
 */
SLOAlarms.apiLatencyAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new api_latency_metric_1.ApiLatencyMetric({
        ...typedSlo,
        sloWindow,
    });
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: typedSlo.latencyThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an AppSyncAvailabilitySLO
 */
SLOAlarms.appSyncAvailabilityAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new appsync_availability_metric_1.AppSyncAvailabilityMetric({
        ...typedSlo,
        sloWindow,
    });
    const sloBudget = 1 - typedSlo.sloThreshold;
    let alarmThreshold = 1 - sloWindow.burnRateThreshold * sloBudget;
    alarmThreshold = Math.max(0, Math.min(alarmThreshold, 1));
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: alarmThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an AppSyncLatencySLO
 */
SLOAlarms.appSyncLatencyAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new appsync_latency_metric_1.AppSyncLatencyMetric({
        ...typedSlo,
        sloWindow,
    });
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: typedSlo.latencyThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an CloudfrontAvailabilitySLO
 */
SLOAlarms.cloudfrontAvailabilityAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new cloudfront_availability_metric_1.CloudfrontAvailabilityMetric({
        ...typedSlo,
        sloWindow,
    });
    const sloBudget = 1 - typedSlo.sloThreshold;
    let alarmThreshold = 1 - sloWindow.burnRateThreshold * sloBudget;
    alarmThreshold = Math.max(0, Math.min(alarmThreshold, 1));
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: alarmThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an CloudfrontLatencySLO
 */
SLOAlarms.cloudfrontLatencyAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new cloudfront_latency_metric_1.CloudfrontLatencyMetric({
        ...typedSlo,
        sloWindow,
    });
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: typedSlo.latencyThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an ElasticSearchAvailabilitySLO
 */
SLOAlarms.elasticSearchAvailabilityAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new elasticsearch_availability_metric_1.ElasticSearchAvailabilityMetric({
        ...typedSlo,
        sloWindow,
    });
    const sloBudget = 1 - typedSlo.sloThreshold;
    let alarmThreshold = 1 - sloWindow.burnRateThreshold * sloBudget;
    alarmThreshold = Math.max(0, Math.min(alarmThreshold, 1));
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: alarmThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an ElasticSearchLatencySLO
 */
SLOAlarms.elasticSearchLatencyAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new elasticsearch_latency_metric_1.ElasticSearchLatencyMetric({
        ...typedSlo,
        sloWindow,
    });
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: typedSlo.latencyThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an CustomAvailabilitySLO
 */
SLOAlarms.customAvailabilityAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new custom_availability_metric_1.CustomAvailabilityMetric({
        ...typedSlo,
        sloWindow,
    });
    const sloBudget = 1 - typedSlo.sloThreshold;
    let alarmThreshold = 1 - sloWindow.burnRateThreshold * sloBudget;
    alarmThreshold = Math.max(0, Math.min(alarmThreshold, 1));
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: alarmThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for constructing an Alarm based on an CustomLatencySLO
 */
SLOAlarms.customLatencyAlarm = (sloWindow, scope, slo, alarmName) => {
    const typedSlo = slo;
    const metric = new custom_latency_metric_1.CustomLatencyMetric({
        ...typedSlo,
        sloWindow,
    });
    return new aws_cdk_lib_1.aws_cloudwatch.Alarm(scope, alarmName, {
        metric,
        threshold: typedSlo.latencyThreshold,
        alarmName,
        evaluationPeriods: 1,
        datapointsToAlarm: 1,
        comparisonOperator: aws_cdk_lib_1.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    });
};
/**
 * Factory method for creating a composite alarm expression from the grid of alarms
 */
SLOAlarms.createCompositeAlarm = (childAlarms) => {
    return childAlarms.reduce((previousOr, currentOr) => {
        const ands = currentOr.reduce((previousAnd, currentAnd) => previousAnd === ''
            ? `ALARM("${currentAnd.alarmName}")`
            : `${previousAnd} AND ALARM("${currentAnd.alarmName}")`, '');
        return previousOr === '' ? `(${ands})` : `${previousOr} OR (${ands})`;
    }, '');
};
/**
 * Factory method for creating all multi-window, multi-burn rate alarms for an SLO
 */
SLOAlarms.createAlarms = (windows, scope, alarmDescription, topics, slo, parentName, alarmFactory) => {
    const alarms = Object.keys(topics).map(topicSeverity => {
        var _a;
        // Create child alarms for all windows associated with this severity
        const childAlarms = windows.reduce((previous, current) => {
            if (current.severity === topicSeverity) {
                const windowsForSeverity = current.windows.map(sloWindow => {
                    return alarmFactory(sloWindow, scope, slo, `${parentName} - ${sloWindow.description}`);
                });
                previous.push(windowsForSeverity);
            }
            return previous;
        }, []);
        // Create a composite alarm that sends its notification to the topic associated with this severity
        const parentSeverityName = `${parentName} (${topicSeverity} Severity)`;
        const parentAlarm = new aws_cdk_lib_1.aws_cloudwatch.CfnCompositeAlarm(scope, parentSeverityName, {
            alarmName: parentSeverityName,
            alarmRule: SLOAlarms.createCompositeAlarm(childAlarms),
            alarmDescription,
            alarmActions: [topics[topicSeverity].topicArn],
            actionsEnabled: slo.alarmsEnabled === undefined ? true : (_a = slo.alarmsEnabled[topicSeverity]) !== null && _a !== void 0 ? _a : true,
        });
        return { severity: topicSeverity, parentAlarm, childAlarms };
    });
    return alarms;
};
