azkaban.utils.Emailer.java Source code

Java tutorial

Introduction

Here is the source code for azkaban.utils.Emailer.java

Source

/*
 * Copyright 2012 LinkedIn Corp.
 *
 * 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 azkaban.utils;

import static java.util.Objects.requireNonNull;

import azkaban.Constants;
import azkaban.Constants.ConfigurationKeys;
import azkaban.alert.Alerter;
import azkaban.executor.ExecutableFlow;
import azkaban.executor.ExecutorLoader;
import azkaban.executor.ExecutorManagerException;
import azkaban.executor.mail.DefaultMailCreator;
import azkaban.executor.mail.MailCreator;
import azkaban.metrics.CommonMetrics;
import azkaban.sla.SlaOption;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.mail.internet.AddressException;
import org.apache.log4j.Logger;

@Singleton
public class Emailer extends AbstractMailer implements Alerter {

    private static final String HTTPS = "https";
    private static final String HTTP = "http";
    private static final Logger logger = Logger.getLogger(Emailer.class);
    private final CommonMetrics commonMetrics;
    private final String scheme;
    private final String clientHostname;
    private final String clientPortNumber;
    private final String azkabanName;
    private final ExecutorLoader executorLoader;

    @Inject
    public Emailer(final Props props, final CommonMetrics commonMetrics, final EmailMessageCreator messageCreator,
            final ExecutorLoader executorLoader) {
        super(props, messageCreator);
        this.executorLoader = requireNonNull(executorLoader, "executorLoader is null.");
        this.commonMetrics = requireNonNull(commonMetrics, "commonMetrics is null.");
        this.azkabanName = props.getString("azkaban.name", "azkaban");

        final int mailTimeout = props.getInt("mail.timeout.millis", 30000);
        EmailMessage.setTimeout(mailTimeout);
        final int connectionTimeout = props.getInt("mail.connection.timeout.millis", 30000);
        EmailMessage.setConnectionTimeout(connectionTimeout);

        EmailMessage.setTotalAttachmentMaxSize(getAttachmentMaxSize());

        this.clientHostname = props.getString(ConfigurationKeys.AZKABAN_WEBSERVER_EXTERNAL_HOSTNAME,
                props.getString("jetty.hostname", "localhost"));

        if (props.getBoolean("jetty.use.ssl", true)) {
            this.scheme = HTTPS;
            this.clientPortNumber = Integer
                    .toString(props.getInt(ConfigurationKeys.AZKABAN_WEBSERVER_EXTERNAL_SSL_PORT,
                            props.getInt("jetty.ssl.port", Constants.DEFAULT_SSL_PORT_NUMBER)));
        } else {
            this.scheme = HTTP;
            this.clientPortNumber = Integer.toString(props.getInt(ConfigurationKeys.AZKABAN_WEBSERVER_EXTERNAL_PORT,
                    props.getInt("jetty.port", Constants.DEFAULT_PORT_NUMBER)));
        }
    }

    public String getAzkabanURL() {
        return this.scheme + "://" + this.clientHostname + ":" + this.clientPortNumber;
    }

    /**
     * Send an email to the specified email list
     */
    public void sendEmail(final List<String> emailList, final String subject, final String body) {
        if (emailList != null && !emailList.isEmpty()) {
            final EmailMessage message = super.createEmailMessage(subject, "text/html", emailList);
            message.setBody(body);
            sendEmail(message, true, "email message " + body);
        }
    }

    @Override
    public void alertOnSla(final SlaOption slaOption, final String slaMessage) {
        final String subject = "SLA violation for " + getJobOrFlowName(slaOption) + " on " + getAzkabanName();
        final List<String> emailList = (List<String>) slaOption.getInfo().get(SlaOption.INFO_EMAIL_LIST);
        logger.info("Sending SLA email " + slaMessage);
        sendEmail(emailList, subject, slaMessage);
    }

    @Override
    public void alertOnFirstError(final ExecutableFlow flow) {
        final EmailMessage message = this.messageCreator.createMessage();
        final MailCreator mailCreator = getMailCreator(flow);
        final boolean mailCreated = mailCreator.createFirstErrorMessage(flow, message, this.azkabanName,
                this.scheme, this.clientHostname, this.clientPortNumber);
        sendEmail(message, mailCreated, "first error email message for execution " + flow.getExecutionId());
    }

    @Override
    public void alertOnError(final ExecutableFlow flow, final String... extraReasons) {
        final EmailMessage message = this.messageCreator.createMessage();
        final MailCreator mailCreator = getMailCreator(flow);
        List<ExecutableFlow> last72hoursExecutions = new ArrayList<>();

        if (flow.getStartTime() > 0) {
            final long startTime = flow.getStartTime() - Duration.ofHours(72).toMillis();
            try {
                last72hoursExecutions = this.executorLoader.fetchFlowHistory(flow.getProjectId(), flow.getFlowId(),
                        startTime);
            } catch (final ExecutorManagerException e) {
                logger.error("unable to fetch past executions", e);
            }
        }

        final boolean mailCreated = mailCreator.createErrorEmail(flow, last72hoursExecutions, message,
                this.azkabanName, this.scheme, this.clientHostname, this.clientPortNumber, extraReasons);
        sendEmail(message, mailCreated, "error email message for execution " + flow.getExecutionId());
    }

    @Override
    public void alertOnSuccess(final ExecutableFlow flow) {
        final EmailMessage message = this.messageCreator.createMessage();
        final MailCreator mailCreator = getMailCreator(flow);
        final boolean mailCreated = mailCreator.createSuccessEmail(flow, message, this.azkabanName, this.scheme,
                this.clientHostname, this.clientPortNumber);
        sendEmail(message, mailCreated, "success email message for execution" + flow.getExecutionId());
    }

    private MailCreator getMailCreator(final ExecutableFlow flow) {
        final String name = flow.getExecutionOptions().getMailCreator();
        return getMailCreator(name);
    }

    private MailCreator getMailCreator(final String name) {
        final MailCreator mailCreator = DefaultMailCreator.getCreator(name);
        logger.debug("ExecutorMailer using mail creator:" + mailCreator.getClass().getCanonicalName());
        return mailCreator;
    }

    public void sendEmail(final EmailMessage message, final boolean mailCreated, final String operation) {
        if (mailCreated) {
            try {
                message.sendEmail();
                logger.info("Sent " + operation);
                this.commonMetrics.markSendEmailSuccess();
            } catch (final Exception e) {
                logger.error("Failed to send " + operation, e);
                if (!(e instanceof AddressException)) {
                    this.commonMetrics.markSendEmailFail();
                }
            }
        }
    }

    private String getJobOrFlowName(final SlaOption slaOption) {
        final String flowName = (String) slaOption.getInfo().get(SlaOption.INFO_FLOW_NAME);
        final String jobName = (String) slaOption.getInfo().get(SlaOption.INFO_JOB_NAME);
        if (org.apache.commons.lang.StringUtils.isNotBlank(jobName)) {
            return flowName + ":" + jobName;
        } else {
            return flowName;
        }
    }
}