Author: virag
Date: Fri May 31 00:35:39 2013
New Revision: 1488068
URL: http://svn.apache.org/r1488068
Log:
OOZIE-1294 SLA Email Notification (ryota via virag)
Added:
oozie/trunk/core/src/main/java/org/apache/oozie/sla/listener/SLAEmailEventListener.java
oozie/trunk/core/src/test/java/org/apache/oozie/sla/TestSLAEmailEventListener.java
Modified:
oozie/trunk/core/src/main/java/org/apache/oozie/action/email/EmailActionExecutor.java
oozie/trunk/core/src/main/java/org/apache/oozie/sla/service/SLAService.java
oozie/trunk/core/src/main/resources/oozie-default.xml
oozie/trunk/release-log.txt
Modified:
oozie/trunk/core/src/main/java/org/apache/oozie/action/email/EmailActionExecutor.java
URL:
http://svn.apache.org/viewvc/oozie/trunk/core/src/main/java/org/apache/oozie/action/email/EmailActionExecutor.java?rev=1488068&r1=1488067&r2=1488068&view=diff
==============================================================================
---
oozie/trunk/core/src/main/java/org/apache/oozie/action/email/EmailActionExecutor.java
(original)
+++
oozie/trunk/core/src/main/java/org/apache/oozie/action/email/EmailActionExecutor.java
Fri May 31 00:35:39 2013
@@ -6,9 +6,9 @@
* to you 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.
@@ -47,6 +47,14 @@ import org.jdom.Namespace;
*/
public class EmailActionExecutor extends ActionExecutor {
+ public static final String CONF_PREFIX = "oozie.email.";
+ public static final String EMAIL_SMTP_HOST = CONF_PREFIX + "smtp.host";
+ public static final String EMAIL_SMTP_PORT = CONF_PREFIX + "smtp.port";
+ public static final String EMAIL_SMTP_AUTH = CONF_PREFIX + "smtp.auth";
+ public static final String EMAIL_SMTP_USER = CONF_PREFIX + "smtp.username";
+ public static final String EMAIL_SMTP_PASS = CONF_PREFIX + "smtp.password";
+ public static final String EMAIL_SMTP_FROM = CONF_PREFIX + "from.address";
+
private final static String TO = "to";
private final static String CC = "cc";
private final static String SUB = "subject";
@@ -112,14 +120,12 @@ public class EmailActionExecutor extends
protected void email(Context context, String[] to, String[] cc, String
subject, String body) throws ActionExecutorException {
// Get mailing server details.
- String smtpHost = getOozieConf().get("oozie.email.smtp.host",
- "localhost");
- String smtpPort = getOozieConf().get("oozie.email.smtp.port", "25");
- Boolean smtpAuth = getOozieConf().getBoolean("oozie.email.smtp.auth",
false);
- String smtpUser = getOozieConf().get("oozie.email.smtp.username", "");
- String smtpPassword = getOozieConf().get("oozie.email.smtp.password",
"");
- String fromAddr = getOozieConf().get("oozie.email.from.address",
- "oozie@localhost");
+ String smtpHost = getOozieConf().get(EMAIL_SMTP_HOST, "localhost");
+ String smtpPort = getOozieConf().get(EMAIL_SMTP_PORT, "25");
+ Boolean smtpAuth = getOozieConf().getBoolean(EMAIL_SMTP_AUTH, false);
+ String smtpUser = getOozieConf().get(EMAIL_SMTP_USER, "");
+ String smtpPassword = getOozieConf().get(EMAIL_SMTP_PASS, "");
+ String fromAddr = getOozieConf().get(EMAIL_SMTP_FROM,
"oozie@localhost");
Properties properties = new Properties();
properties.setProperty("mail.smtp.host", smtpHost);
@@ -207,7 +213,7 @@ public class EmailActionExecutor extends
return true;
}
- private static class JavaMailAuthenticator extends Authenticator {
+ public static class JavaMailAuthenticator extends Authenticator {
String user;
String password;
Added:
oozie/trunk/core/src/main/java/org/apache/oozie/sla/listener/SLAEmailEventListener.java
URL:
http://svn.apache.org/viewvc/oozie/trunk/core/src/main/java/org/apache/oozie/sla/listener/SLAEmailEventListener.java?rev=1488068&view=auto
==============================================================================
---
oozie/trunk/core/src/main/java/org/apache/oozie/sla/listener/SLAEmailEventListener.java
(added)
+++
oozie/trunk/core/src/main/java/org/apache/oozie/sla/listener/SLAEmailEventListener.java
Fri May 31 00:35:39 2013
@@ -0,0 +1,403 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.oozie.sla.listener;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.NoSuchProviderException;
+import javax.mail.SendFailedException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMessage.RecipientType;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.oozie.action.email.EmailActionExecutor;
+import org.apache.oozie.action.email.EmailActionExecutor.JavaMailAuthenticator;
+import org.apache.oozie.client.event.SLAEvent;
+import org.apache.oozie.sla.listener.SLAEventListener;
+import org.apache.oozie.sla.service.SLAService;
+import org.apache.oozie.util.XLog;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+public class SLAEmailEventListener extends SLAEventListener {
+
+ public static final String SMTP_CONNECTION_TIMEOUT =
EmailActionExecutor.CONF_PREFIX + "smtp.connectiontimeout";
+ public static final String SMTP_TIMEOUT = EmailActionExecutor.CONF_PREFIX
+ "smtp.timeout";
+ public static final String BLACKLIST_CACHE_TIMEOUT =
EmailActionExecutor.CONF_PREFIX + "blacklist.cachetimeout";
+ public static final String BLACKLIST_FAIL_COUNT =
EmailActionExecutor.CONF_PREFIX + "blacklist.failcount";
+ public static final String OOZIE_BASE_URL = "oozie.base.url";
+ private Session session;
+ private String oozieBaseUrl;
+ private InternetAddress fromAddr;
+ private String ADDRESS_SEPARATOR = ",";
+ private LoadingCache<String, AtomicInteger> blackList;
+ private int blacklistFailCount;
+ private final String BLACKLIST_CACHE_TIMEOUT_DEFAULT = "1800"; // in sec.
default to 30 min
+ private final String BLACKLIST_FAIL_COUNT_DEFAULT = "2"; // stop sending
when fail count equals or exceeds
+ private final String SMTP_HOST_DEFAULT = "localhost";
+ private final String SMTP_PORT_DEFAULT = "25";
+ private final boolean SMTP_AUTH_DEFAULT = false;
+ private final String SMTP_SOURCE_DEFAULT = "oozie@localhost";
+ private final String SMTP_CONNECTION_TIMEOUT_DEFAULT = "5000";
+ private final String SMTP_TIMEOUT_DEFAULT = "5000";
+ private static XLog LOG = XLog.getLog(SLAEmailEventListener.class);
+ private Set<SLAEvent.EventStatus> alertEvents;
+ public static String EMAIL_BODY_FIELD_SEPARATER = " - ";
+ public static String EMAIL_BODY_FIELD_INDENT = " ";
+ public static String EMAIL_BODY_HEADER_SEPARATER = ":";
+
+ public enum EmailField {
+ EVENT_STATUS("SLA Status"), APP_TYPE("App Type"), APP_NAME("App
Name"), USER("User"), JOBID("Job ID"), PARENT_JOBID(
+ "Parent Job ID"), JOB_URL("Job URL"), PARENT_JOB_URL("Parent
Job URL"), NOMINAL_TIME("Nominal Time"),
+ EXPECTED_START_TIME("Expected Start Time"),
ACTUAL_START_TIME("Actual Start Time"),
+ EXPECTED_END_TIME("Expected End Time"),
ACTUAL_END_TIME("Actual End Time"), EXPECTED_DURATION("Expected Duration"),
+ ACTUAL_DURATION("Actual Duration"),
NOTIFICATION_MESSAGE("Notification Message"), UPSTREAM_APPS("Upstream Apps"),
+ JOB_STATUS("Job Status");
+ private String name;
+
+ private EmailField(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+ };
+
+ @Override
+ public void init(Configuration conf) throws Exception {
+
+ oozieBaseUrl = conf.get(OOZIE_BASE_URL);
+ // Get SMTP properties from the configuration used in Email Action
+ String smtpHost = conf.get(EmailActionExecutor.EMAIL_SMTP_HOST,
SMTP_HOST_DEFAULT);
+ String smtpPort = conf.get(EmailActionExecutor.EMAIL_SMTP_PORT,
SMTP_PORT_DEFAULT);
+ Boolean smtpAuth =
conf.getBoolean(EmailActionExecutor.EMAIL_SMTP_AUTH, SMTP_AUTH_DEFAULT);
+ String smtpUser = conf.get(EmailActionExecutor.EMAIL_SMTP_USER, "");
+ String smtpPassword = conf.get(EmailActionExecutor.EMAIL_SMTP_PASS,
"");
+ String smtpConnectTimeout = conf.get(SMTP_CONNECTION_TIMEOUT,
SMTP_CONNECTION_TIMEOUT_DEFAULT);
+ String smtpTimeout = conf.get(SMTP_TIMEOUT, SMTP_TIMEOUT_DEFAULT);
+
+ int blacklistTimeOut =
Integer.valueOf(conf.get(BLACKLIST_CACHE_TIMEOUT,
BLACKLIST_CACHE_TIMEOUT_DEFAULT));
+ blacklistFailCount = Integer.valueOf(conf.get(BLACKLIST_FAIL_COUNT,
BLACKLIST_FAIL_COUNT_DEFAULT));
+
+ // blacklist email addresses causing SendFailedException with cache
timeout
+ blackList = CacheBuilder.newBuilder()
+ .expireAfterWrite(blacklistTimeOut, TimeUnit.SECONDS)
+ .build(new CacheLoader<String, AtomicInteger>() {
+ public AtomicInteger load(String key) throws Exception {
+ return new AtomicInteger();
+ }
+ });
+
+ // Set SMTP properties
+ Properties properties = new Properties();
+ properties.setProperty("mail.smtp.host", smtpHost);
+ properties.setProperty("mail.smtp.port", smtpPort);
+ properties.setProperty("mail.smtp.auth", smtpAuth.toString());
+ properties.setProperty("mail.smtp.connectiontimeout",
smtpConnectTimeout);
+ properties.setProperty("mail.smtp.timeout", smtpTimeout);
+
+ try {
+ fromAddr = new
InternetAddress(conf.get("oozie.email.from.address", SMTP_SOURCE_DEFAULT));
+ }
+ catch (AddressException ae) {
+ LOG.error("Bad Source Address specified in
oozie.email.from.address", ae);
+ throw ae;
+ }
+
+ if (!smtpAuth) {
+ session = Session.getInstance(properties);
+ }
+ else {
+ session = Session.getInstance(properties, new
JavaMailAuthenticator(smtpUser, smtpPassword));
+ }
+
+ alertEvents = new HashSet<SLAEvent.EventStatus>();
+ String alertEventsStr = conf.get(SLAService.CONF_ALERT_EVENTS);
+ if (alertEventsStr != null) {
+ String[] alertEvt = alertEventsStr.split(",", -1);
+ for (String evt : alertEvt) {
+ alertEvents.add(SLAEvent.EventStatus.valueOf(evt));
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ private void sendSLAEmail(SLAEvent event) throws Exception {
+ Message message = new MimeMessage(session);
+ setMessageHeader(message, event);
+ setMessageBody(message, event);
+ sendEmail(message);
+ }
+
+ @Override
+ public void onStartMiss(SLAEvent event) {
+ boolean flag = false;
+ if (event.getAlertEvents() == null) {
+ flag = alertEvents.contains(SLAEvent.EventStatus.START_MISS);
+ }
+ else if
(event.getAlertEvents().contains(SLAEvent.EventStatus.START_MISS.name())) {
+ flag = true;
+ }
+
+ if (flag) {
+ try {
+ sendSLAEmail(event);
+ }
+ catch (Exception e) {
+ LOG.error("Failed to send StartMiss alert email", e);
+ }
+ }
+ }
+
+ @Override
+ public void onEndMiss(SLAEvent event) {
+ boolean flag = false;
+ if (event.getAlertEvents() == null) {
+ flag = alertEvents.contains(SLAEvent.EventStatus.END_MISS);
+ }
+ else if
(event.getAlertEvents().contains(SLAEvent.EventStatus.END_MISS.name())) {
+ flag = true;
+ }
+
+ if (flag) {
+ try {
+ sendSLAEmail(event);
+ }
+ catch (Exception e) {
+ LOG.error("Failed to send EndMiss alert email", e);
+ }
+ }
+ }
+
+ @Override
+ public void onDurationMiss(SLAEvent event) {
+ boolean flag = false;
+ if (event.getAlertEvents() == null) {
+ flag = alertEvents.contains(SLAEvent.EventStatus.DURATION_MISS);
+ }
+ else if
(event.getAlertEvents().contains(SLAEvent.EventStatus.DURATION_MISS.name())) {
+ flag = true;
+ }
+
+ if (flag) {
+ try {
+ sendSLAEmail(event);
+ }
+ catch (Exception e) {
+ LOG.error("Failed to send DurationMiss alert email", e);
+ }
+ }
+ }
+
+ private Address[] parseAddress(String str) {
+ Address[] addrs = null;
+ List<InternetAddress> addrList = new ArrayList<InternetAddress>();
+ String[] emails = str.split(ADDRESS_SEPARATOR, -1);
+
+ for (String email : emails) {
+ boolean isBlackListed = false;
+ AtomicInteger val = blackList.getIfPresent(email);
+ if(val != null){
+ isBlackListed = ( val.get() >= blacklistFailCount );
+ }
+ if (!isBlackListed) {
+ try {
+ // turn on strict syntax check by setting 2nd argument true
+ addrList.add(new InternetAddress(email, true));
+ }
+ catch (AddressException ae) {
+ // simply skip bad address but do not throw exception
+ LOG.error("Skipping bad destination address: " + email,
ae);
+ }
+ }
+ }
+
+ if (addrList.size() > 0)
+ addrs = (Address[]) addrList.toArray(new
InternetAddress[addrList.size()]);
+
+ return addrs;
+ }
+
+ private void setMessageHeader(Message msg, SLAEvent event) throws
MessagingException {
+ Address[] from = new InternetAddress[] { fromAddr };
+ Address[] to;
+ StringBuilder subject = new StringBuilder();
+
+ to = parseAddress(event.getAlertContact());
+ if (to == null) {
+ LOG.error("Destination address is null or invalid, stop sending
SLA alert email");
+ throw new IllegalArgumentException("Destination address is not
specified properly");
+ }
+ subject.append("OOZIE - SLA ");
+ subject.append(event.getEventStatus().name());
+ subject.append(" (AppName=");
+ subject.append(event.getAppName());
+ subject.append(", JobID=");
+ subject.append(event.getId());
+ subject.append(")");
+
+ try {
+ msg.addFrom(from);
+ msg.addRecipients(RecipientType.TO, to);
+ msg.setSubject(subject.toString());
+ }
+ catch (MessagingException me) {
+ LOG.error("Message Exception in setting message header of SLA
alert email", me);
+ throw me;
+ }
+ }
+
+ private void setMessageBody(Message msg, SLAEvent event) throws
MessagingException {
+ StringBuilder body = new StringBuilder();
+ printHeading(body, "Status");
+ printField(body, EmailField.EVENT_STATUS.toString(),
event.getEventStatus(), false);
+ printField(body, EmailField.JOB_STATUS.toString(),
event.getJobStatus(), false);
+ printField(body, EmailField.NOTIFICATION_MESSAGE.toString(),
event.getNotificationMsg(), false);
+
+ printHeading(body, "Job Details");
+ printField(body, EmailField.APP_NAME.toString(), event.getAppName(),
false);
+ printField(body, EmailField.APP_TYPE.toString(), event.getAppType(),
false);
+ printField(body, EmailField.USER.toString(), event.getUser(), false);
+ printField(body, EmailField.JOBID.toString(), event.getId(), false);
+ printField(body, EmailField.JOB_URL.toString(),
getJobLink(event.getId()), false);
+ printField(body, EmailField.PARENT_JOBID.toString(),
event.getParentId() != null ? event.getParentId() : "N/A",
+ false);
+ printField(body, EmailField.PARENT_JOB_URL.toString(),
+ event.getParentId() != null ? getJobLink(event.getParentId())
: "N/A", false);
+ printField(body, EmailField.UPSTREAM_APPS.toString(),
event.getUpstreamApps(), false);
+
+ printHeading(body, "SLA Details");
+ printField(body, EmailField.NOMINAL_TIME.toString(),
event.getNominalTime(), false);
+ printField(body, EmailField.EXPECTED_START_TIME.toString(),
event.getExpectedStart(), false);
+ printField(body, EmailField.ACTUAL_START_TIME.toString(),
event.getActualStart(), false);
+ printField(body, EmailField.EXPECTED_END_TIME.toString(),
event.getExpectedEnd(), false);
+ printField(body, EmailField.ACTUAL_END_TIME.toString(),
event.getActualEnd(), false);
+ printField(body, EmailField.EXPECTED_DURATION.toString(),
event.getExpectedDuration(), false);
+ long actualDuration = (event.getActualEnd() != null &&
event.getActualStart() != null) ? (event.getActualEnd()
+ .getTime() - event.getActualStart().getTime()) / (1000 * 60) :
-1;
+ printField(body, EmailField.ACTUAL_DURATION.toString(),
actualDuration, false);
+
+ try {
+ msg.setText(body.toString());
+ }
+ catch (MessagingException me) {
+ LOG.error("Message Exception in setting message body of SLA alert
email", me);
+ throw me;
+ }
+ }
+
+ private String getJobLink(String jobId) {
+ StringBuffer url = new StringBuffer();
+ String param = "/?job=";
+ url.append(oozieBaseUrl);
+ url.append(param);
+ url.append(jobId);
+ return url.toString();
+ }
+
+ private void printField(StringBuilder st, String name, Object value,
boolean last) {
+ String lineFeed = "\n";
+ if (value != null) {
+ st.append(EMAIL_BODY_FIELD_INDENT);
+ st.append(name);
+ st.append(EMAIL_BODY_FIELD_SEPARATER);
+ st.append(value);
+ if (!last)
+ st.append(lineFeed);
+ }
+ }
+
+ private void printHeading(StringBuilder st, String header) {
+ st.append(header);
+ st.append(EMAIL_BODY_HEADER_SEPARATER);
+ st.append("\n");
+ }
+
+ private void sendEmail(Message message) throws MessagingException {
+ try {
+ Transport.send(message);
+ }
+ catch (NoSuchProviderException se) {
+ LOG.error("Could not find an SMTP transport provider to email",
se);
+ throw se;
+ }
+ catch (MessagingException me) {
+ LOG.error("Message Exception in transporting SLA alert email", me);
+ if (me instanceof SendFailedException) {
+ Address[] invalidAddrs = ((SendFailedException)
me).getInvalidAddresses();
+ if (invalidAddrs != null && invalidAddrs.length > 0) {
+ for (Address addr : invalidAddrs) {
+ try {
+ // 'get' method loads key into cache when it
doesn't exist
+ AtomicInteger val = blackList.get(addr.toString());
+ val.incrementAndGet();
+ }
+ catch (Exception e) {
+ LOG.debug("blacklist loading throwed exception");
+ }
+ }
+ }
+ }
+ throw me;
+ }
+ }
+
+ @VisibleForTesting
+ public void addBlackList(String email) throws Exception {
+ // this is for testing
+ if(email == null || email.equals("")){
+ return;
+ }
+ AtomicInteger val = blackList.get(email);
+ val.set(blacklistFailCount);
+ }
+
+ @Override
+ public void onStartMet(SLAEvent work) {
+ }
+
+ @Override
+ public void onEndMet(SLAEvent work) {
+ }
+
+ @Override
+ public void onDurationMet(SLAEvent work) {
+ }
+
+}
Modified:
oozie/trunk/core/src/main/java/org/apache/oozie/sla/service/SLAService.java
URL:
http://svn.apache.org/viewvc/oozie/trunk/core/src/main/java/org/apache/oozie/sla/service/SLAService.java?rev=1488068&r1=1488067&r2=1488068&view=diff
==============================================================================
--- oozie/trunk/core/src/main/java/org/apache/oozie/sla/service/SLAService.java
(original)
+++ oozie/trunk/core/src/main/java/org/apache/oozie/sla/service/SLAService.java
Fri May 31 00:35:39 2013
@@ -39,7 +39,7 @@ public class SLAService implements Servi
public static final String CONF_PREFIX = "oozie.sla.service.SLAService.";
public static final String CONF_CALCULATOR_IMPL = CONF_PREFIX +
"calculator.impl";
public static final String CONF_CAPACITY = CONF_PREFIX + "capacity";
- public static final String CONF_ALERT_TYPES = CONF_PREFIX + "alert.types";
+ public static final String CONF_ALERT_EVENTS = CONF_PREFIX +
"alert.events";
private static SLACalculator calcImpl;
private static boolean slaEnabled = false;
Modified: oozie/trunk/core/src/main/resources/oozie-default.xml
URL:
http://svn.apache.org/viewvc/oozie/trunk/core/src/main/resources/oozie-default.xml?rev=1488068&r1=1488067&r2=1488068&view=diff
==============================================================================
--- oozie/trunk/core/src/main/resources/oozie-default.xml (original)
+++ oozie/trunk/core/src/main/resources/oozie-default.xml Fri May 31 00:35:39
2013
@@ -1892,7 +1892,7 @@
</property>
<property>
- <name>oozie.sla.service.SLAService.alert.types</name>
+ <name>oozie.sla.service.SLAService.alert.events</name>
<value>END_MISS</value>
<description>
Default types of SLA events for being alerted of.
Added:
oozie/trunk/core/src/test/java/org/apache/oozie/sla/TestSLAEmailEventListener.java
URL:
http://svn.apache.org/viewvc/oozie/trunk/core/src/test/java/org/apache/oozie/sla/TestSLAEmailEventListener.java?rev=1488068&view=auto
==============================================================================
---
oozie/trunk/core/src/test/java/org/apache/oozie/sla/TestSLAEmailEventListener.java
(added)
+++
oozie/trunk/core/src/test/java/org/apache/oozie/sla/TestSLAEmailEventListener.java
Fri May 31 00:35:39 2013
@@ -0,0 +1,393 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.oozie.sla;
+
+import java.util.Date;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.oozie.AppType;
+import org.apache.oozie.action.email.EmailActionExecutor;
+import org.apache.oozie.client.event.JobEvent;
+import org.apache.oozie.client.event.SLAEvent;
+import org.apache.oozie.client.event.SLAEvent.EventStatus;
+import org.apache.oozie.service.Services;
+import org.apache.oozie.sla.SLACalcStatus;
+import org.apache.oozie.sla.SLARegistrationBean;
+import org.apache.oozie.test.XTestCase;
+import org.apache.oozie.util.DateUtils;
+import org.junit.After;
+import org.junit.Before;
+import com.icegreen.greenmail.util.GreenMail;
+import com.icegreen.greenmail.util.ServerSetup;
+import javax.mail.Message.RecipientType;
+import javax.mail.internet.MimeMessage;
+import org.apache.oozie.sla.listener.SLAEmailEventListener;
+import org.apache.oozie.sla.listener.SLAEmailEventListener.EmailField;
+import org.apache.oozie.sla.service.SLAService;
+
+public class TestSLAEmailEventListener extends XTestCase {
+
+ private Services services;
+ private static final int SMTP_TEST_PORT = 3025;
+ private GreenMail greenMail;
+ private SLAEmailEventListener slaEmailListener;
+ private Configuration conf = null;
+
+ @Before
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ services = new Services();
+ conf = services.getConf();
+ conf.set(EmailActionExecutor.EMAIL_SMTP_HOST, "localhost");
+ conf.set(EmailActionExecutor.EMAIL_SMTP_PORT,
String.valueOf(SMTP_TEST_PORT));
+ conf.set(EmailActionExecutor.EMAIL_SMTP_AUTH, "false");
+ conf.set(EmailActionExecutor.EMAIL_SMTP_USER, "");
+ conf.set(EmailActionExecutor.EMAIL_SMTP_PASS, "");
+ conf.set(EmailActionExecutor.EMAIL_SMTP_FROM, "oozie@localhost");
+ conf.set(SLAEmailEventListener.BLACKLIST_CACHE_TIMEOUT, "1");
+ conf.set(SLAEmailEventListener.BLACKLIST_FAIL_COUNT, "2");
+ conf.set(SLAService.CONF_ALERT_EVENTS,
SLAEvent.EventStatus.START_MISS.name() + ","
+ + SLAEvent.EventStatus.END_MISS + "," +
SLAEvent.EventStatus.DURATION_MISS);
+
+ greenMail = new GreenMail(new ServerSetup(SMTP_TEST_PORT, null,
"smtp"));
+ greenMail.start();
+ services.init();
+ slaEmailListener = new SLAEmailEventListener();
+ slaEmailListener.init(conf);
+ }
+
+ @After
+ @Override
+ protected void tearDown() throws Exception {
+ if (null != greenMail) {
+ greenMail.stop();
+ }
+ services.destroy();
+ super.tearDown();
+ }
+
+ public void testOnStartMiss() throws Exception {
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ Date startDate = DateUtils.parseDateUTC("2013-01-01T00:00Z");
+ Date actualstartDate = DateUtils.parseDateUTC("2013-01-01T01:00Z");
+ event.setEventStatus(EventStatus.START_MISS);
+ event.setJobStatus(JobEvent.EventStatus.STARTED.toString());
+ event.setJobId("0000000-000000000000001-oozie-wrkf-C@1");
+ eventBean.setParentId("0000000-000000000000001-oozie-wrkf-C");
+ eventBean.setAppName("Test-SLA-Start-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setNominalTime(startDate);
+ eventBean.setExpectedStart(startDate);
+ eventBean.setNotificationMsg("Notification of Missing Expected Start
Time");
+ eventBean.setAlertContact("[email protected]");
+ event.setActualStart(actualstartDate);
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+
+ slaEmailListener.onStartMiss(event);
+
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ MimeMessage msg = msgs[0];
+ // check message header
+ assertEquals(msg.getFrom()[0].toString(), "oozie@localhost");
+ assertEquals(msg.getRecipients(RecipientType.TO)[0].toString(),
"[email protected]");
+ assertEquals(msg.getSubject(), "OOZIE - SLA " + EventStatus.START_MISS
+ + " (AppName=Test-SLA-Start-Miss,
JobID=0000000-000000000000001-oozie-wrkf-C@1)");
+ // check message body
+ String msgBody = msg.getContent().toString();
+ String headerSep = SLAEmailEventListener.EMAIL_BODY_HEADER_SEPARATER;
+ String sep = SLAEmailEventListener.EMAIL_BODY_FIELD_SEPARATER;
+ String indent = SLAEmailEventListener.EMAIL_BODY_FIELD_INDENT;
+ assertTrue(msgBody.indexOf("Status" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.EVENT_STATUS.toString()
+ sep
+ + EventStatus.START_MISS.toString()) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOB_STATUS.toString() +
sep
+ + JobEvent.EventStatus.STARTED.toString()) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.NOTIFICATION_MESSAGE.toString() + sep
+ + "Notification of Missing Expected Start Time") > -1);
+ assertTrue(msgBody.indexOf("Job Details" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.APP_TYPE.toString() +
sep + AppType.COORDINATOR_ACTION) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.APP_NAME.toString() +
sep + "Test-SLA-Start-Miss") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.USER.toString() + sep +
"dummyuser") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOBID.toString() + sep
+ + "0000000-000000000000001-oozie-wrkf-C@1") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.PARENT_JOBID.toString()
+ sep
+ + "0000000-000000000000001-oozie-wrkf-C") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOB_URL.toString() + sep
+ + conf.get(SLAEmailEventListener.OOZIE_BASE_URL) + "/?job=" +
"0000000-000000000000001-oozie-wrkf-C@1") > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.PARENT_JOB_URL.toString() + sep
+ + conf.get(SLAEmailEventListener.OOZIE_BASE_URL) + "/?job=" +
"0000000-000000000000001-oozie-wrkf-C") > -1);
+ assertTrue(msgBody.indexOf("SLA Details" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.NOMINAL_TIME.toString()
+ sep + startDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.EXPECTED_START_TIME.toString() + sep + startDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.ACTUAL_START_TIME.toString() + sep + actualstartDate) > -1);
+ }
+
+ public void testOnEndMiss() throws Exception {
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ Date expectedStartDate = DateUtils.parseDateUTC("2013-01-01T00:00Z");
+ Date actualStartDate = DateUtils.parseDateUTC("2013-01-01T01:00Z");
+ Date expectedEndDate = DateUtils.parseDateUTC("2013-01-01T12:00Z");
+ Date actualEndDate = DateUtils.parseDateUTC("2013-01-01T13:00Z");
+ event.setJobId("0000000-000000000000001-oozie-wrkf-C@1");
+ eventBean.setParentId("0000000-000000000000001-oozie-wrkf-C");
+ event.setEventStatus(EventStatus.END_MISS);
+ event.setJobStatus(JobEvent.EventStatus.SUCCESS.toString());
+ eventBean.setAppName("Test-SLA-End-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setNominalTime(expectedStartDate);
+ eventBean.setExpectedStart(expectedStartDate);
+ eventBean.setExpectedEnd(expectedEndDate);
+ eventBean.setNotificationMsg("notification of end miss");
+ eventBean.setAlertContact("[email protected]");
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ eventBean.setExpectedStart(expectedStartDate);
+ eventBean.setExpectedEnd(expectedEndDate);
+ event.setActualStart(actualStartDate);
+ event.setActualEnd(actualEndDate);
+
+ slaEmailListener.onEndMiss(event);
+
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ MimeMessage msg = msgs[0];
+ // check message header
+ assertEquals(msg.getFrom()[0].toString(), "oozie@localhost");
+ assertEquals(msg.getRecipients(RecipientType.TO)[0].toString(),
"[email protected]");
+ assertEquals(msg.getSubject(), "OOZIE - SLA " + EventStatus.END_MISS
+ + " (AppName=Test-SLA-End-Miss,
JobID=0000000-000000000000001-oozie-wrkf-C@1)");
+ // check message body
+ String msgBody = msg.getContent().toString();
+ String headerSep = SLAEmailEventListener.EMAIL_BODY_HEADER_SEPARATER;
+ String sep = SLAEmailEventListener.EMAIL_BODY_FIELD_SEPARATER;
+ String indent = SLAEmailEventListener.EMAIL_BODY_FIELD_INDENT;
+ assertTrue(msgBody.indexOf("Status" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.EVENT_STATUS.toString()
+ sep + EventStatus.END_MISS.toString()) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOB_STATUS.toString() +
sep
+ + JobEvent.EventStatus.SUCCESS.toString()) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.NOTIFICATION_MESSAGE.toString() + sep
+ + "notification of end miss") > -1);
+
+ assertTrue(msgBody.indexOf("Job Details" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.APP_TYPE.toString() +
sep + AppType.COORDINATOR_ACTION) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.APP_NAME.toString() +
sep + "Test-SLA-End-Miss") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.USER.toString() + sep +
"dummyuser") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOBID.toString() + sep
+ + "0000000-000000000000001-oozie-wrkf-C@1") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.PARENT_JOBID.toString()
+ sep
+ + "0000000-000000000000001-oozie-wrkf-C") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOB_URL.toString() + sep
+ + conf.get(SLAEmailEventListener.OOZIE_BASE_URL) + "/?job=" +
"0000000-000000000000001-oozie-wrkf-C@1") > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.PARENT_JOB_URL.toString() + sep
+ + conf.get(SLAEmailEventListener.OOZIE_BASE_URL) + "/?job=" +
"0000000-000000000000001-oozie-wrkf-C") > -1);
+
+ assertTrue(msgBody.indexOf("SLA Details" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.NOMINAL_TIME.toString()
+ sep + expectedStartDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.EXPECTED_START_TIME.toString() + sep + expectedStartDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.ACTUAL_START_TIME.toString() + sep + actualStartDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.EXPECTED_END_TIME.toString() + sep + expectedEndDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.ACTUAL_END_TIME.toString() + sep + actualEndDate) > -1);
+
+ }
+
+ public void testOnDurationMiss() throws Exception {
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ Date expectedStartDate = DateUtils.parseDateUTC("2013-01-01T00:00Z");
+ Date actualStartDate = DateUtils.parseDateUTC("2013-01-01T00:10Z");
+ Date expectedEndDate = DateUtils.parseDateUTC("2013-01-01T00:20Z");
+ Date actualEndDate = DateUtils.parseDateUTC("2013-01-01T00:40Z");
+ long expectedDuration = (expectedEndDate.getTime() -
expectedStartDate.getTime()) / (1000 * 60);
+ long actualDuration = (actualEndDate.getTime() -
actualStartDate.getTime()) / (1000 * 60);
+ event.setJobId("0000000-000000000000001-oozie-wrkf-C@1");
+ eventBean.setParentId("0000000-000000000000001-oozie-wrkf-C");
+ event.setEventStatus(EventStatus.DURATION_MISS);
+ event.setJobStatus(JobEvent.EventStatus.SUCCESS.toString());
+ eventBean.setAppName("Test-SLA-Duration-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setNominalTime(expectedStartDate);
+ eventBean.setExpectedStart(expectedStartDate);
+ eventBean.setExpectedEnd(expectedEndDate);
+ eventBean.setNotificationMsg("notification of duration miss");
+ eventBean.setAlertContact("[email protected]");
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ eventBean.setExpectedStart(expectedStartDate);
+ eventBean.setExpectedEnd(expectedEndDate);
+ event.setActualStart(actualStartDate);
+ event.setActualEnd(actualEndDate);
+ eventBean.setExpectedDuration(expectedDuration);
+ event.setActualDuration(actualDuration);
+
+ slaEmailListener.onEndMiss(event);
+
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ MimeMessage msg = msgs[0];
+ // check message header
+ assertEquals(msg.getFrom()[0].toString(), "oozie@localhost");
+ assertEquals(msg.getRecipients(RecipientType.TO)[0].toString(),
"[email protected]");
+ assertEquals(msg.getSubject(), "OOZIE - SLA " +
EventStatus.DURATION_MISS
+ + " (AppName=Test-SLA-Duration-Miss,
JobID=0000000-000000000000001-oozie-wrkf-C@1)");
+ // check message body
+ String msgBody = msg.getContent().toString();
+ String headerSep = SLAEmailEventListener.EMAIL_BODY_HEADER_SEPARATER;
+ String sep = SLAEmailEventListener.EMAIL_BODY_FIELD_SEPARATER;
+ String indent = SLAEmailEventListener.EMAIL_BODY_FIELD_INDENT;
+ assertTrue(msgBody.indexOf("Status" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.EVENT_STATUS.toString()
+ sep
+ + EventStatus.DURATION_MISS.toString()) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOB_STATUS.toString() +
sep
+ + JobEvent.EventStatus.SUCCESS.toString()) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.NOTIFICATION_MESSAGE.toString() + sep
+ + "notification of duration miss") > -1);
+
+ assertTrue(msgBody.indexOf("Job Details" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.APP_TYPE.toString() +
sep + AppType.COORDINATOR_ACTION) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.APP_NAME.toString() +
sep + "Test-SLA-Duration-Miss") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.USER.toString() + sep +
"dummyuser") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOBID.toString() + sep
+ + "0000000-000000000000001-oozie-wrkf-C@1") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.PARENT_JOBID.toString()
+ sep
+ + "0000000-000000000000001-oozie-wrkf-C") > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.JOB_URL.toString() + sep
+ + conf.get(SLAEmailEventListener.OOZIE_BASE_URL) + "/?job=" +
"0000000-000000000000001-oozie-wrkf-C@1") > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.PARENT_JOB_URL.toString() + sep
+ + conf.get(SLAEmailEventListener.OOZIE_BASE_URL) + "/?job=" +
"0000000-000000000000001-oozie-wrkf-C") > -1);
+
+ assertTrue(msgBody.indexOf("SLA Details" + headerSep) > -1);
+ assertTrue(msgBody.indexOf(indent + EmailField.NOMINAL_TIME.toString()
+ sep + expectedStartDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.EXPECTED_START_TIME.toString() + sep + expectedStartDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.ACTUAL_START_TIME.toString() + sep + actualStartDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.EXPECTED_END_TIME.toString() + sep + expectedEndDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.ACTUAL_END_TIME.toString() + sep + actualEndDate) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.EXPECTED_DURATION.toString() + sep + expectedDuration) > -1);
+ assertTrue(msgBody.indexOf(indent +
EmailField.ACTUAL_DURATION.toString() + sep + actualDuration) > -1);
+ }
+
+ public void testUserAlertEventSetting() throws Exception {
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ // user choose only END MISS, thus, START_MISS email should not be sent
+ eventBean.setAlertEvents(EventStatus.END_MISS.name());
+
+ Date startDate = DateUtils.parseDateUTC("2013-01-01T00:00Z");
+ Date actualstartDate = DateUtils.parseDateUTC("2013-01-01T01:00Z");
+ event.setEventStatus(EventStatus.START_MISS);
+ event.setJobId("0000000-000000000000001-oozie-wrkf-C@1");
+ eventBean.setAppName("Test-SLA-Start-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setNominalTime(startDate);
+ eventBean.setExpectedStart(startDate);
+ eventBean.setAlertContact("[email protected]");
+ event.setActualStart(actualstartDate);
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+
+ slaEmailListener.onStartMiss(event);
+
+ // START_MISS should not be sent
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ assertEquals(msgs.length, 0);
+
+ // DURATION_MISS should not be sent
+ event.setEventStatus(EventStatus.DURATION_MISS);
+ slaEmailListener.onDurationMiss(event);
+ msgs = greenMail.getReceivedMessages();
+ assertEquals(msgs.length, 0);
+
+ // END_MISS should be sent
+ event.setEventStatus(EventStatus.END_MISS);
+ slaEmailListener.onEndMiss(event);
+ msgs = greenMail.getReceivedMessages();
+ assertNotNull(msgs[0]);
+ }
+
+ public void testInvalidDestAddress() throws Exception {
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ Date startDate = DateUtils.parseDateUTC("2013-01-01T00:00Z");
+ // set invalid address as alert contact
+ eventBean.setAlertContact("invalidAddress");
+ event.setEventStatus(EventStatus.START_MISS);
+ event.setJobId("0000000-000000000000001-oozie-wrkf-C@1");
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ eventBean.setAppName("Test-SLA-Start-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setExpectedStart(startDate);
+ eventBean.setNotificationMsg("notification of start miss");
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ event.setActualStart(DateUtils.parseDateUTC("2013-01-01T01:00Z"));
+
+ slaEmailListener.onStartMiss(event);
+
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ assertEquals(msgs.length, 0);
+ }
+
+ public void testMultipleDestAddress() throws Exception {
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ Date startDate = DateUtils.parseDateUTC("2013-01-01T00:00Z");
+ // set multiple addresses as alert contact
+ eventBean.setAlertContact("[email protected],
[email protected]");
+ event.setEventStatus(EventStatus.START_MISS);
+ event.setJobId("0000000-000000000000001-oozie-wrkf-C@1");
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ eventBean.setAppName("Test-SLA-Start-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setExpectedStart(startDate);
+ eventBean.setNotificationMsg("notification of start miss");
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ event.setActualStart(DateUtils.parseDateUTC("2013-01-01T01:00Z"));
+
+ slaEmailListener.onStartMiss(event);
+
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ MimeMessage msg = msgs[0];
+ assertEquals(msg.getFrom()[0].toString(), "oozie@localhost");
+ assertEquals(msg.getRecipients(RecipientType.TO)[0].toString(),
"[email protected]");
+ assertEquals(msg.getRecipients(RecipientType.TO)[1].toString(),
"[email protected]");
+ }
+
+ public void testBlackList() throws Exception {
+ String blackListedEmail = "[email protected]";
+ // add email to blacklist
+ slaEmailListener.addBlackList(blackListedEmail);
+ SLACalcStatus event = new SLACalcStatus(new SLARegistrationBean());
+ SLARegistrationBean eventBean = event.getSLARegistrationBean();
+ event.setEventStatus(EventStatus.START_MISS);
+ eventBean.setAlertContact(blackListedEmail);
+ eventBean.setAppType(AppType.COORDINATOR_ACTION);
+ eventBean.setAppName("Test-SLA-Start-Miss");
+ eventBean.setUser("dummyuser");
+ eventBean.setAlertContact("[email protected]");
+ event.setActualStart(DateUtils.parseDateUTC("2013-01-01T01:00Z"));
+
+ // blacklist blocks email from being sent out
+ slaEmailListener.onStartMiss(event);
+ MimeMessage[] msgs = greenMail.getReceivedMessages();
+ assertEquals(msgs.length, 0);
+
+ // wait 1.5 sec (cache timeout set to 1sec in test's setup)
+ Thread.sleep(1500);
+
+ // cache is evicted
+ slaEmailListener.onStartMiss(event);
+ msgs = greenMail.getReceivedMessages();
+ assertEquals(msgs.length, 1);
+ }
+}
Modified: oozie/trunk/release-log.txt
URL:
http://svn.apache.org/viewvc/oozie/trunk/release-log.txt?rev=1488068&r1=1488067&r2=1488068&view=diff
==============================================================================
--- oozie/trunk/release-log.txt (original)
+++ oozie/trunk/release-log.txt Fri May 31 00:35:39 2013
@@ -1,5 +1,6 @@
-- Oozie 4.1.0 release (trunk - unreleased)
+OOZIE-1294 SLA Email Notification (ryota via virag)
OOZIE-1395 Using Yarn's CapacityScheduler causes some tests to time out
(rkanter)
OOZIE-1373 Oozie compilation fails with jdk7 (tucu, rohini via rohini)
OOZIE-1296 SLA JMS Event Listener for publishing notifications related to SLA
information (ryota via mona)