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}