001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.sla;
020
021import java.sql.Timestamp;
022import java.util.Date;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Set;
026
027import javax.persistence.Basic;
028import javax.persistence.Column;
029import javax.persistence.Entity;
030import javax.persistence.Id;
031import javax.persistence.NamedQueries;
032import javax.persistence.NamedQuery;
033import javax.persistence.Table;
034
035import com.google.common.annotations.VisibleForTesting;
036import org.apache.oozie.AppType;
037import org.apache.oozie.client.event.SLAEvent;
038import org.apache.oozie.client.event.SLAEvent.EventStatus;
039import org.apache.oozie.client.rest.JsonBean;
040import org.apache.oozie.client.rest.JsonTags;
041import org.apache.oozie.client.rest.JsonUtils;
042import org.apache.oozie.util.DateUtils;
043import org.apache.oozie.util.XLog;
044import org.apache.openjpa.persistence.jdbc.Index;
045import org.json.simple.JSONArray;
046import org.json.simple.JSONObject;
047
048@Entity
049@Table(name = "SLA_SUMMARY")
050@NamedQueries({
051
052 @NamedQuery(name = "UPDATE_SLA_SUMMARY_FOR_SLA_STATUS", query = "update  SLASummaryBean w set w.slaStatus = :slaStatus, w.eventStatus = :eventStatus, w.eventProcessed = :eventProcessed, w.lastModifiedTS = :lastModifiedTS where w.jobId = :jobId"),
053
054 @NamedQuery(name = "UPDATE_SLA_SUMMARY_FOR_STATUS_ACTUAL_TIMES", query = "update SLASummaryBean w set w.slaStatus = :slaStatus, w.eventStatus = :eventStatus, w.eventProcessed = :eventProcessed, w.jobStatus = :jobStatus, w.lastModifiedTS = :lastModifiedTS, w.actualStartTS = :actualStartTS, w.actualEndTS = :actualEndTS, w.actualDuration = :actualDuration where w.jobId = :jobId"),
055
056 @NamedQuery(name = "UPDATE_SLA_SUMMARY_FOR_ACTUAL_TIMES", query = "update SLASummaryBean w set w.eventProcessed = :eventProcessed, w.actualStartTS = :actualStartTS, w.actualEndTS = :actualEndTS, w.actualEndTS = :actualEndTS, w.actualDuration = :actualDuration, w.lastModifiedTS = :lastModifiedTS where w.jobId = :jobId"),
057
058 @NamedQuery(name = "UPDATE_SLA_SUMMARY_EVENTPROCESSED", query = "update SLASummaryBean w set w.eventProcessed = :eventProcessed where w.jobId = :jobId"),
059
060 @NamedQuery(name = "UPDATE_SLA_SUMMARY_ALL", query = "update SLASummaryBean w set w.jobId = :jobId, w.appName = :appName, w.appType = :appType, w.nominalTimeTS = :nominalTime, w.expectedStartTS = :expectedStartTime, w.expectedEndTS = :expectedEndTime, w.expectedDuration = :expectedDuration, w.jobStatus = :jobStatus, w.slaStatus = :slaStatus, w.eventStatus = :eventStatus, w.lastModifiedTS = :lastModTime, w.user = :user, w.parentId = :parentId, w.eventProcessed = :eventProcessed, w.actualDuration = :actualDuration, w.actualEndTS = :actualEndTS, w.actualStartTS = :actualStartTS where w.jobId = :jobId"),
061
062 @NamedQuery(name = "GET_SLA_SUMMARY", query = "select OBJECT(w) from SLASummaryBean w where w.jobId = :id"),
063
064 @NamedQuery(name = "GET_SLA_SUMMARY_RECORDS_RESTART", query = "select OBJECT(w) from SLASummaryBean w where w.eventProcessed <= 7 AND w.lastModifiedTS >= :lastModifiedTime"),
065
066 @NamedQuery(name = "GET_SLA_SUMMARY_EVENTPROCESSED", query = "select w.eventProcessed from SLASummaryBean w where w.jobId = :id"),
067
068 @NamedQuery(name = "GET_SLA_SUMMARY_ALL", query = "select OBJECT(w) from SLASummaryBean w")
069})
070
071/**
072 * Class to store all the SLA related details (summary) per job
073 */
074public class SLASummaryBean implements JsonBean {
075    private static final XLog LOG = XLog.getLog(SLASummaryBean.class);
076
077    public static final String EVENT_STATUS_SEPARATOR = ",";
078    public static final String EVENT_STATUS_ALL = "ALL";
079
080    @Id
081    @Basic
082    @Column(name = "job_id")
083    private String jobId;
084
085    @Basic
086    @Index
087    @Column(name = "parent_id")
088    private String parentId;
089
090    @Basic
091    @Index
092    @Column(name = "app_name")
093    private String appName;
094
095    @Basic
096    @Column(name = "app_type")
097    private String appType;
098
099    @Basic
100    @Column(name = "user_name")
101    private String user;
102
103    @Basic
104    @Column(name = "created_time")
105    private Timestamp createdTimeTS = null;
106
107    @Basic
108    @Index
109    @Column(name = "nominal_time")
110    private Timestamp nominalTimeTS = null;
111
112    @Basic
113    @Column(name = "expected_start")
114    private Timestamp expectedStartTS = null;
115
116    @Basic
117    @Column(name = "expected_end")
118    private Timestamp expectedEndTS = null;
119
120    @Basic
121    @Column(name = "expected_duration")
122    private long expectedDuration = -1;
123
124    @Basic
125    @Column(name = "actual_start")
126    private Timestamp actualStartTS = null;
127
128    @Basic
129    @Column(name = "actual_end")
130    private Timestamp actualEndTS = null;
131
132    @Basic
133    @Column(name = "actual_duration")
134    private long actualDuration = -1;
135
136    @Basic
137    @Column(name = "job_status")
138    private String jobStatus;
139
140    @Basic
141    @Column(name = "event_status")
142    private String eventStatus;
143
144    @Basic
145    @Column(name = "sla_status")
146    private String slaStatus;
147
148    @Basic
149    @Index
150    @Column(name = "event_processed")
151    private byte eventProcessed = 0;
152
153    @Basic
154    @Index
155    @Column(name = "last_modified")
156    private Timestamp lastModifiedTS = null;
157
158    public SLASummaryBean() {
159    }
160
161    public SLASummaryBean(SLACalcStatus slaCalc) {
162        SLARegistrationBean reg = slaCalc.getSLARegistrationBean();
163        setId(slaCalc.getId());
164        setAppName(reg.getAppName());
165        setAppType(reg.getAppType());
166        setNominalTime(reg.getNominalTime());
167        setExpectedStart(reg.getExpectedStart());
168        setExpectedEnd(reg.getExpectedEnd());
169        setExpectedDuration(reg.getExpectedDuration());
170        setJobStatus(slaCalc.getJobStatus());
171        setSLAStatus(slaCalc.getSLAStatus());
172        setEventStatus(slaCalc.getEventStatus());
173        setLastModifiedTime(slaCalc.getLastModifiedTime());
174        setUser(reg.getUser());
175        setParentId(reg.getParentId());
176        setEventProcessed(slaCalc.getEventProcessed());
177        setActualDuration(slaCalc.getActualDuration());
178        setActualEnd(slaCalc.getActualEnd());
179        setActualStart(slaCalc.getActualStart());
180    }
181
182    public String getId() {
183        return jobId;
184    }
185
186    public void setId(String jobId) {
187        this.jobId = jobId;
188    }
189
190    public String getParentId() {
191        return parentId;
192    }
193
194    public void setParentId(String parentId) {
195        this.parentId = parentId;
196    }
197
198    public Timestamp getCreatedTimestamp() {
199        return createdTimeTS;
200    }
201
202    public void setCreatedTimestamp(Timestamp createdTime) {
203        this.createdTimeTS = createdTime;
204    }
205
206    public Date getCreatedTime() {
207        return DateUtils.toDate(createdTimeTS);
208    }
209
210    public void setCreatedTime(Date createdTime) {
211        this.createdTimeTS = DateUtils.convertDateToTimestamp(createdTime);
212    }
213
214    public Date getNominalTime() {
215        return DateUtils.toDate(nominalTimeTS);
216    }
217
218    public Timestamp getNominalTimestamp() {
219        return this.nominalTimeTS;
220    }
221
222    public void setNominalTime(Date nominalTime) {
223        this.nominalTimeTS = DateUtils.convertDateToTimestamp(nominalTime);
224    }
225
226
227    public Date getExpectedStart() {
228        return DateUtils.toDate(expectedStartTS);
229    }
230
231    public Timestamp getExpectedStartTimestamp() {
232        return this.expectedStartTS;
233    }
234
235    public void setExpectedStart(Date expectedStart) {
236        this.expectedStartTS = DateUtils.convertDateToTimestamp(expectedStart);
237    }
238
239    public Date getExpectedEnd() {
240        return DateUtils.toDate(expectedEndTS);
241    }
242
243    public Timestamp getExpectedEndTimestamp() {
244        return this.expectedEndTS;
245    }
246    public void setExpectedEnd(Date expectedEnd) {
247        this.expectedEndTS = DateUtils.convertDateToTimestamp(expectedEnd);
248    }
249
250    public long getExpectedDuration() {
251        return expectedDuration;
252    }
253
254    public void setExpectedDuration(long expectedDuration) {
255        this.expectedDuration = expectedDuration;
256    }
257
258    public Date getActualStart() {
259        return DateUtils.toDate(actualStartTS);
260    }
261
262    public Timestamp getActualStartTimestamp() {
263        return this.actualStartTS;
264    }
265
266    public void setActualStart(Date actualStart) {
267        this.actualStartTS = DateUtils.convertDateToTimestamp(actualStart);
268    }
269
270    public Date getActualEnd() {
271        return DateUtils.toDate(actualEndTS);
272    }
273
274    public Timestamp getActualEndTimestamp() {
275        return this.actualEndTS;
276    }
277
278    public void setActualEnd(Date actualEnd) {
279        this.actualEndTS = DateUtils.convertDateToTimestamp(actualEnd);
280    }
281
282    public long getActualDuration() {
283        return actualDuration;
284    }
285
286    public void setActualDuration(long actualDuration) {
287        this.actualDuration = actualDuration;
288    }
289
290    public String getJobStatus() {
291        return jobStatus;
292    }
293
294    public void setJobStatus(String status) {
295        this.jobStatus = status;
296    }
297
298    public SLAEvent.EventStatus getEventStatus() {
299        return (eventStatus != null ? SLAEvent.EventStatus.valueOf(eventStatus) : null);
300    }
301
302    public void setEventStatus(SLAEvent.EventStatus eventStatus) {
303        this.eventStatus = (eventStatus != null ? eventStatus.name() : null);
304    }
305
306    public SLAEvent.SLAStatus getSLAStatus() {
307        return (slaStatus != null ? SLAEvent.SLAStatus.valueOf(slaStatus) : null);
308    }
309
310    public String getSLAStatusString() {
311        return slaStatus;
312    }
313
314    public String getEventStatusString() {
315        return eventStatus;
316    }
317
318    public void setSLAStatus(SLAEvent.SLAStatus stage) {
319        this.slaStatus = (stage != null ? stage.name() : null);
320    }
321
322    public String getUser() {
323        return user;
324    }
325
326    public void setUser(String user) {
327        this.user = user;
328    }
329
330    public String getAppName() {
331        return appName;
332    }
333
334    public void setAppName(String appName) {
335        this.appName = appName;
336    }
337
338    public AppType getAppType() {
339        return AppType.valueOf(appType);
340    }
341
342    public void setAppType(AppType appType) {
343        this.appType = appType.toString();
344    }
345
346    public byte getEventProcessed() {
347        return eventProcessed;
348    }
349
350    public void setEventProcessed(int eventProcessed) {
351        this.eventProcessed = (byte)eventProcessed;
352    }
353
354    public Date getLastModifiedTime() {
355        return DateUtils.toDate(lastModifiedTS);
356    }
357
358    public Timestamp getLastModifiedTimestamp() {
359        return this.lastModifiedTS;
360    }
361
362    public void setLastModifiedTime(Date lastModified) {
363        this.lastModifiedTS = DateUtils.convertDateToTimestamp(lastModified);
364    }
365
366    @SuppressWarnings("unchecked")
367    @Override
368    public JSONObject toJSONObject() {
369        final JSONObject json = new JSONObject();
370
371        json.put(JsonTags.SLA_SUMMARY_ID, jobId);
372        if (parentId != null) {
373            json.put(JsonTags.SLA_SUMMARY_PARENT_ID, parentId);
374        }
375        json.put(JsonTags.SLA_SUMMARY_APP_NAME, appName);
376        json.put(JsonTags.SLA_SUMMARY_APP_TYPE, appType);
377        json.put(JsonTags.SLA_SUMMARY_USER, user);
378        json.put(JsonTags.SLA_SUMMARY_NOMINAL_TIME, nominalTimeTS.getTime());
379        if (expectedStartTS != null) {
380            json.put(JsonTags.SLA_SUMMARY_EXPECTED_START, expectedStartTS.getTime());
381        }
382        else {
383            json.put(JsonTags.SLA_SUMMARY_EXPECTED_START, null);
384        }
385        if (actualStartTS != null) {
386            json.put(JsonTags.SLA_SUMMARY_ACTUAL_START, actualStartTS.getTime());
387        }
388        else {
389            json.put(JsonTags.SLA_SUMMARY_ACTUAL_START, null);
390        }
391        json.put(JsonTags.SLA_SUMMARY_EXPECTED_END, expectedEndTS.getTime());
392        if (actualEndTS != null) {
393            json.put(JsonTags.SLA_SUMMARY_ACTUAL_END, actualEndTS.getTime());
394        }
395        else {
396            json.put(JsonTags.SLA_SUMMARY_ACTUAL_END, null);
397        }
398        json.put(JsonTags.SLA_SUMMARY_EXPECTED_DURATION, expectedDuration);
399        json.put(JsonTags.SLA_SUMMARY_ACTUAL_DURATION, actualDuration);
400        json.put(JsonTags.SLA_SUMMARY_JOB_STATUS, jobStatus);
401        json.put(JsonTags.SLA_SUMMARY_SLA_STATUS, slaStatus);
402        json.put(JsonTags.SLA_SUMMARY_EVENT_STATUS, new SLAEventStatusCalculator(this).calculate().toString());
403        json.put(JsonTags.SLA_SUMMARY_LAST_MODIFIED, lastModifiedTS.getTime());
404
405        return json;
406    }
407
408    @SuppressWarnings("unchecked")
409    @Override
410    public JSONObject toJSONObject(String timeZoneId) {
411        if (timeZoneId == null) {
412            return toJSONObject();
413        }
414        else {
415            JSONObject json = new JSONObject();
416            json.put(JsonTags.SLA_SUMMARY_ID, jobId);
417            if (parentId != null) {
418                json.put(JsonTags.SLA_SUMMARY_PARENT_ID, parentId);
419            }
420            json.put(JsonTags.SLA_SUMMARY_APP_NAME, appName);
421            json.put(JsonTags.SLA_SUMMARY_APP_TYPE, appType);
422            json.put(JsonTags.SLA_SUMMARY_USER, user);
423            json.put(JsonTags.SLA_SUMMARY_NOMINAL_TIME, JsonUtils.formatDateRfc822(nominalTimeTS, timeZoneId));
424            if (expectedStartTS != null) {
425                json.put(JsonTags.SLA_SUMMARY_EXPECTED_START, JsonUtils.formatDateRfc822(expectedStartTS, timeZoneId));
426            }
427            else {
428                json.put(JsonTags.SLA_SUMMARY_EXPECTED_START, null);
429            }
430            if (actualStartTS != null) {
431                json.put(JsonTags.SLA_SUMMARY_ACTUAL_START, JsonUtils.formatDateRfc822(actualStartTS, timeZoneId));
432            }
433            else {
434                json.put(JsonTags.SLA_SUMMARY_ACTUAL_START, null);
435            }
436            json.put(JsonTags.SLA_SUMMARY_EXPECTED_END, JsonUtils.formatDateRfc822(expectedEndTS, timeZoneId));
437            if (actualEndTS != null) {
438                json.put(JsonTags.SLA_SUMMARY_ACTUAL_END, JsonUtils.formatDateRfc822(actualEndTS, timeZoneId));
439            }
440            else {
441                json.put(JsonTags.SLA_SUMMARY_ACTUAL_END, null);
442            }
443            json.put(JsonTags.SLA_SUMMARY_EXPECTED_DURATION, expectedDuration);
444            json.put(JsonTags.SLA_SUMMARY_ACTUAL_DURATION, actualDuration);
445            json.put(JsonTags.SLA_SUMMARY_JOB_STATUS, jobStatus);
446            json.put(JsonTags.SLA_SUMMARY_SLA_STATUS, slaStatus);
447            json.put(JsonTags.SLA_SUMMARY_EVENT_STATUS, new SLAEventStatusCalculator(this).calculate().toString());
448            json.put(JsonTags.SLA_SUMMARY_LAST_MODIFIED, JsonUtils.formatDateRfc822(lastModifiedTS, timeZoneId));
449
450            return json;
451        }
452    }
453
454    @VisibleForTesting
455    static class SLAEventStatusCalculator {
456        private final Set<EventStatus> events = new LinkedHashSet<>();
457        private final SLASummaryBean slaSummaryBean;
458
459        SLAEventStatusCalculator(final SLASummaryBean slaSummaryBean) {
460            this.slaSummaryBean = slaSummaryBean;
461        }
462
463        SLAEventStatusCalculator calculate() {
464            events.clear();
465
466            addStartEvents();
467
468            addDurationEvents();
469
470            addEndEvents();
471
472            return this;
473        }
474
475        private void addStartEvents() {
476            if (slaSummaryBean.expectedStartTS != null) {
477                if (slaSummaryBean.actualStartTS != null) {
478                    final long diff = (slaSummaryBean.actualStartTS.getTime() - slaSummaryBean.expectedStartTS.getTime()) / 60_000;
479                    if (diff > 0) {
480                        events.add(EventStatus.START_MISS);
481                    }
482                    else {
483                        events.add(EventStatus.START_MET);
484                    }
485                }
486                else {
487                    final long diff = (nowMs() - slaSummaryBean.expectedStartTS.getTime()) / 60_000;
488                    if (diff > 0) {
489                        events.add(EventStatus.START_MISS);
490                    }
491                }
492            }
493        }
494
495        private long nowMs() {
496            return new Date().getTime();
497        }
498
499        private void addDurationEvents() {
500            if (slaSummaryBean.expectedDuration != -1) {
501                if (slaSummaryBean.actualDuration != -1) {
502                    final long diff = slaSummaryBean.actualDuration - slaSummaryBean.expectedDuration;
503                    if (diff > 0) {
504                        events.add(EventStatus.DURATION_MISS);
505                    }
506                    else {
507                        events.add(EventStatus.DURATION_MET);
508                    }
509                }
510                else {
511                    if (slaSummaryBean.actualStartTS != null) {
512                        final long currentDur = nowMs() - slaSummaryBean.actualStartTS.getTime();
513                        if (slaSummaryBean.expectedDuration < currentDur) {
514                            events.add(EventStatus.DURATION_MISS);
515                        }
516                    }
517                }
518            }
519        }
520
521        public void addEndEvents() {
522            if (slaSummaryBean.expectedEndTS != null) {
523                if (slaSummaryBean.actualEndTS != null) {
524                    final long diff = (slaSummaryBean.actualEndTS.getTime() - slaSummaryBean.expectedEndTS.getTime()) / 60_000;
525                    if (diff > 0) {
526                        events.add(EventStatus.END_MISS);
527                    }
528                    else {
529                        events.add(EventStatus.END_MET);
530                    }
531                }
532                else {
533                    final long diff = (nowMs() - slaSummaryBean.expectedEndTS.getTime()) / 60_000;
534                    if (diff > 0) {
535                        events.add(EventStatus.END_MISS);
536                    }
537                }
538            }
539        }
540
541        @Override
542        public String toString() {
543            final StringBuilder eventStatusBuilder = new StringBuilder();
544
545            boolean first = true;
546            for (final EventStatus e: events) {
547                if (!first) {
548                    eventStatusBuilder.append(EVENT_STATUS_SEPARATOR);
549                }
550
551                eventStatusBuilder.append(e.toString());
552                first = false;
553            }
554
555            return eventStatusBuilder.toString();
556        }
557    }
558
559    /**
560     * Convert a sla summary list into a json object.
561     *
562     * @param slaSummaryList sla summary list.
563     * @param timeZoneId time zone to use for dates in the JSON array.
564     * @param includeEventStatus whether to include {@link SLASummaryBean#eventStatus} in the JSON object
565     * @return the corresponding JSON object
566     */
567    @SuppressWarnings("unchecked")
568    public static JSONObject toJSONObject(final List<? extends SLASummaryBean> slaSummaryList,
569                                          final String timeZoneId,
570                                          final boolean includeEventStatus) {
571
572        LOG.debug("Transforming to JSON object. [slaSummaryList.size={0};timeZoneId={1};includeEventStatus={2}]",
573                slaSummaryList.size(),
574                timeZoneId,
575                includeEventStatus);
576
577        final JSONObject jsonObject = new JSONObject();
578        final JSONArray jsonArray = new JSONArray();
579
580        for (final SLASummaryBean summary : slaSummaryList) {
581            final JSONObject summaryJson = summary.toJSONObject(timeZoneId);
582
583            final boolean removeEventStatus = !includeEventStatus && summaryJson.containsKey(JsonTags.SLA_SUMMARY_EVENT_STATUS);
584            if (removeEventStatus) {
585                LOG.trace("Removing event status. [{0}={1}]",
586                        JsonTags.SLA_SUMMARY_ID,
587                        summaryJson.get(JsonTags.SLA_SUMMARY_ID));
588
589                summaryJson.remove(JsonTags.SLA_SUMMARY_EVENT_STATUS);
590            }
591
592            jsonArray.add(summaryJson);
593        }
594
595        jsonObject.put(JsonTags.SLA_SUMMARY_LIST, jsonArray);
596
597        LOG.debug("Returning JSON object. [jsonArray.size={0}]", jsonArray.size());
598
599        return jsonObject;
600    }
601
602}