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; 020 021import java.io.IOException; 022import java.io.Writer; 023import java.sql.Timestamp; 024import java.text.ParseException; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.LinkedHashSet; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036import java.util.StringTokenizer; 037 038import org.apache.commons.lang.StringUtils; 039import org.apache.hadoop.conf.Configuration; 040import org.apache.oozie.client.CoordinatorAction; 041import org.apache.oozie.client.CoordinatorJob; 042import org.apache.oozie.client.OozieClient; 043import org.apache.oozie.client.WorkflowJob; 044import org.apache.oozie.client.rest.RestConstants; 045import org.apache.oozie.command.CommandException; 046import org.apache.oozie.command.OperationType; 047import org.apache.oozie.command.coord.BulkCoordXCommand; 048import org.apache.oozie.command.coord.CoordActionInfoXCommand; 049import org.apache.oozie.command.coord.CoordActionsIgnoreXCommand; 050import org.apache.oozie.command.coord.CoordActionsKillXCommand; 051import org.apache.oozie.command.coord.CoordChangeXCommand; 052import org.apache.oozie.command.coord.CoordJobXCommand; 053import org.apache.oozie.command.coord.CoordJobsXCommand; 054import org.apache.oozie.command.coord.CoordKillXCommand; 055import org.apache.oozie.command.coord.CoordRerunXCommand; 056import org.apache.oozie.command.coord.CoordResumeXCommand; 057import org.apache.oozie.command.coord.CoordSubmitXCommand; 058import org.apache.oozie.command.coord.CoordSuspendXCommand; 059import org.apache.oozie.command.coord.CoordUpdateXCommand; 060import org.apache.oozie.executor.jpa.CoordActionQueryExecutor; 061import org.apache.oozie.executor.jpa.CoordJobQueryExecutor; 062import org.apache.oozie.executor.jpa.JPAExecutorException; 063import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor; 064import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor.WorkflowJobQuery; 065import org.apache.oozie.service.DagXLogInfoService; 066import org.apache.oozie.service.Services; 067import org.apache.oozie.service.XLogStreamingService; 068import org.apache.oozie.util.CoordActionsInDateRange; 069import org.apache.oozie.util.DateUtils; 070import org.apache.oozie.util.Pair; 071import org.apache.oozie.util.ParamChecker; 072import org.apache.oozie.util.XLog; 073import org.apache.oozie.util.XLogFilter; 074import org.apache.oozie.util.XLogUserFilterParam; 075 076import com.google.common.annotations.VisibleForTesting; 077 078public class CoordinatorEngine extends BaseEngine { 079 private static final XLog LOG = XLog.getLog(CoordinatorEngine.class); 080 public final static String COORD_ACTIONS_LOG_MAX_COUNT = "oozie.coord.actions.log.max.count"; 081 private final static int COORD_ACTIONS_LOG_MAX_COUNT_DEFAULT = 50; 082 private final int maxNumActionsForLog; 083 084 public enum FILTER_COMPARATORS { 085 //This ordering is important, dont change this 086 GREATER_EQUAL(">="), GREATER(">"), LESSTHAN_EQUAL("<="), LESSTHAN("<"), NOT_EQUALS("!="), EQUALS("="); 087 088 private final String sign; 089 090 FILTER_COMPARATORS(String sign) { 091 this.sign = sign; 092 } 093 094 public String getSign() { 095 return sign; 096 } 097 } 098 099 public static final String[] VALID_JOB_FILTERS = {OozieClient.FILTER_STATUS, OozieClient.FILTER_NOMINAL_TIME}; 100 101 /** 102 * Create a system Coordinator engine, with no user and no group. 103 */ 104 public CoordinatorEngine() { 105 maxNumActionsForLog = Services.get().getConf() 106 .getInt(COORD_ACTIONS_LOG_MAX_COUNT, COORD_ACTIONS_LOG_MAX_COUNT_DEFAULT); 107 } 108 109 /** 110 * Create a Coordinator engine to perform operations on behave of a user. 111 * 112 * @param user user name. 113 */ 114 public CoordinatorEngine(String user) { 115 this(); 116 this.user = ParamChecker.notEmpty(user, "user"); 117 } 118 119 /* 120 * (non-Javadoc) 121 * 122 * @see org.apache.oozie.BaseEngine#getDefinition(java.lang.String) 123 */ 124 @Override 125 public String getDefinition(String jobId) throws BaseEngineException { 126 CoordinatorJobBean job = getCoordJobWithNoActionInfo(jobId); 127 return job.getOrigJobXml(); 128 } 129 130 /** 131 * @param jobId 132 * @return CoordinatorJobBean 133 * @throws BaseEngineException 134 */ 135 private CoordinatorJobBean getCoordJobWithNoActionInfo(String jobId) throws BaseEngineException { 136 try { 137 return new CoordJobXCommand(jobId).call(); 138 } 139 catch (CommandException ex) { 140 throw new BaseEngineException(ex); 141 } 142 } 143 144 /** 145 * @param actionId 146 * @return CoordinatorActionBean 147 * @throws BaseEngineException 148 */ 149 public CoordinatorActionBean getCoordAction(String actionId) throws BaseEngineException { 150 try { 151 return new CoordActionInfoXCommand(actionId).call(); 152 } 153 catch (CommandException ex) { 154 throw new BaseEngineException(ex); 155 } 156 } 157 158 /* 159 * (non-Javadoc) 160 * 161 * @see org.apache.oozie.BaseEngine#getCoordJob(java.lang.String) 162 */ 163 @Override 164 public CoordinatorJobBean getCoordJob(String jobId) throws BaseEngineException { 165 try { 166 return new CoordJobXCommand(jobId).call(); 167 } 168 catch (CommandException ex) { 169 throw new BaseEngineException(ex); 170 } 171 } 172 173 /* 174 * (non-Javadoc) 175 * 176 * @see org.apache.oozie.BaseEngine#getCoordJob(java.lang.String, java.lang.String, int, int) 177 */ 178 @Override 179 public CoordinatorJobBean getCoordJob(String jobId, String filter, int offset, int length, boolean desc) 180 throws BaseEngineException { 181 Map<Pair<String, FILTER_COMPARATORS>, List<Object>> filterMap = parseJobFilter(filter); 182 try { 183 return new CoordJobXCommand(jobId, filterMap, offset, length, desc).call(); 184 } 185 catch (CommandException ex) { 186 throw new BaseEngineException(ex); 187 } 188 } 189 190 /* 191 * (non-Javadoc) 192 * 193 * @see org.apache.oozie.BaseEngine#getJobIdForExternalId(java.lang.String) 194 */ 195 @Override 196 public String getJobIdForExternalId(String externalId) throws CoordinatorEngineException { 197 return null; 198 } 199 200 /* 201 * (non-Javadoc) 202 * 203 * @see org.apache.oozie.BaseEngine#kill(java.lang.String) 204 */ 205 @Override 206 public void kill(String jobId) throws CoordinatorEngineException { 207 try { 208 new CoordKillXCommand(jobId).call(); 209 LOG.info("User " + user + " killed the Coordinator job " + jobId); 210 } 211 catch (CommandException e) { 212 throw new CoordinatorEngineException(e); 213 } 214 } 215 216 public CoordinatorActionInfo killActions(String jobId, String rangeType, String scope) throws CoordinatorEngineException { 217 try { 218 return new CoordActionsKillXCommand(jobId, rangeType, scope).call(); 219 } 220 catch (CommandException e) { 221 throw new CoordinatorEngineException(e); 222 } 223 } 224 225 /* (non-Javadoc) 226 * @see org.apache.oozie.BaseEngine#change(java.lang.String, java.lang.String) 227 */ 228 @Override 229 public void change(String jobId, String changeValue) throws CoordinatorEngineException { 230 try { 231 new CoordChangeXCommand(jobId, changeValue).call(); 232 LOG.info("User " + user + " changed the Coordinator job [" + jobId + "] to " + changeValue); 233 } 234 catch (CommandException e) { 235 throw new CoordinatorEngineException(e); 236 } 237 } 238 239 public CoordinatorActionInfo ignore(String jobId, String type, String scope) throws CoordinatorEngineException { 240 try { 241 LOG.info("User " + user + " ignore a Coordinator Action (s) [" + scope + "] of the Coordinator Job [" 242 + jobId + "]"); 243 return new CoordActionsIgnoreXCommand(jobId, type, scope).call(); 244 } 245 catch (CommandException e) { 246 throw new CoordinatorEngineException(e); 247 } 248 } 249 250 @Override 251 @Deprecated 252 public void reRun(String jobId, Configuration conf) throws BaseEngineException { 253 throw new BaseEngineException(new XException(ErrorCode.E0301, "invalid use of rerun")); 254 } 255 256 /** 257 * Rerun coordinator actions for given rerunType 258 * 259 * @param jobId 260 * @param rerunType 261 * @param scope 262 * @param refresh 263 * @param noCleanup 264 * @throws BaseEngineException 265 */ 266 public CoordinatorActionInfo reRun(String jobId, String rerunType, String scope, boolean refresh, boolean noCleanup, 267 boolean failed) 268 throws BaseEngineException { 269 try { 270 return new CoordRerunXCommand(jobId, rerunType, scope, refresh, 271 noCleanup, failed).call(); 272 } 273 catch (CommandException ex) { 274 throw new BaseEngineException(ex); 275 } 276 } 277 278 /* 279 * (non-Javadoc) 280 * 281 * @see org.apache.oozie.BaseEngine#resume(java.lang.String) 282 */ 283 @Override 284 public void resume(String jobId) throws CoordinatorEngineException { 285 try { 286 new CoordResumeXCommand(jobId).call(); 287 } 288 catch (CommandException e) { 289 throw new CoordinatorEngineException(e); 290 } 291 } 292 293 @Override 294 @Deprecated 295 public void start(String jobId) throws BaseEngineException { 296 throw new BaseEngineException(new XException(ErrorCode.E0301, "invalid use of start")); 297 } 298 299 /* 300 * (non-Javadoc) 301 * 302 * @see org.apache.oozie.BaseEngine#streamLog(java.lang.String, 303 * java.io.Writer) 304 */ 305 @Override 306 public void streamLog(String jobId, Writer writer, Map<String, String[]> params) throws IOException, 307 BaseEngineException { 308 309 try { 310 XLogFilter filter = new XLogFilter(new XLogUserFilterParam(params)); 311 filter.setParameter(DagXLogInfoService.JOB, jobId); 312 Date lastTime = null; 313 CoordinatorJobBean job = getCoordJobWithNoActionInfo(jobId); 314 if (job.isTerminalStatus()) { 315 lastTime = job.getLastModifiedTime(); 316 } 317 if (lastTime == null) { 318 lastTime = new Date(); 319 } 320 Services.get().get(XLogStreamingService.class) 321 .streamLog(filter, job.getCreatedTime(), lastTime, writer, params); 322 } 323 catch (Exception e) { 324 throw new IOException(e); 325 } 326 } 327 328 /** 329 * Add list of actions to the filter based on conditions 330 * 331 * @param jobId Job Id 332 * @param logRetrievalScope Value for the retrieval type 333 * @param logRetrievalType Based on which filter criteria the log is retrieved 334 * @param writer writer to stream the log to 335 * @param params additional parameters from the request 336 * @throws IOException 337 * @throws BaseEngineException 338 * @throws CommandException 339 */ 340 public void streamLog(String jobId, String logRetrievalScope, String logRetrievalType, Writer writer, 341 Map<String, String[]> params) throws IOException, BaseEngineException, CommandException { 342 343 Date startTime = null; 344 Date endTime = null; 345 XLogFilter filter = new XLogFilter(new XLogUserFilterParam(params)); 346 347 filter.setParameter(DagXLogInfoService.JOB, jobId); 348 if (logRetrievalScope != null && logRetrievalType != null) { 349 // if coordinator action logs are to be retrieved based on action id range 350 if (logRetrievalType.equals(RestConstants.JOB_LOG_ACTION)) { 351 // Use set implementation that maintains order or elements to achieve reproducibility: 352 Set<String> actionSet = new LinkedHashSet<String>(); 353 String[] list = logRetrievalScope.split(","); 354 for (String s : list) { 355 s = s.trim(); 356 if (s.contains("-")) { 357 String[] range = s.split("-"); 358 if (range.length != 2) { 359 throw new CommandException(ErrorCode.E0302, "format is wrong for action's range '" + s 360 + "'"); 361 } 362 int start; 363 int end; 364 try { 365 start = Integer.parseInt(range[0].trim()); 366 } catch (NumberFormatException ne) { 367 throw new CommandException(ErrorCode.E0302, "could not parse " + range[0].trim() + "into an integer", 368 ne); 369 } 370 try { 371 end = Integer.parseInt(range[1].trim()); 372 } catch (NumberFormatException ne) { 373 throw new CommandException(ErrorCode.E0302, "could not parse " + range[1].trim() + "into an integer", 374 ne); 375 } 376 if (start > end) { 377 throw new CommandException(ErrorCode.E0302, "format is wrong for action's range '" + s + "'"); 378 } 379 for (int i = start; i <= end; i++) { 380 actionSet.add(jobId + "@" + i); 381 } 382 } 383 else { 384 try { 385 Integer.parseInt(s); 386 } 387 catch (NumberFormatException ne) { 388 throw new CommandException(ErrorCode.E0302, "format is wrong for action id'" + s 389 + "'. Integer only."); 390 } 391 actionSet.add(jobId + "@" + s); 392 } 393 } 394 395 if (actionSet.size() >= maxNumActionsForLog) { 396 throw new CommandException(ErrorCode.E0302, 397 "Retrieving log of too many coordinator actions. Max count is " 398 + maxNumActionsForLog + " actions"); 399 } 400 Iterator<String> actionsIterator = actionSet.iterator(); 401 StringBuilder orSeparatedActions = new StringBuilder(""); 402 boolean orRequired = false; 403 while (actionsIterator.hasNext()) { 404 if (orRequired) { 405 orSeparatedActions.append("|"); 406 } 407 orSeparatedActions.append(actionsIterator.next().toString()); 408 orRequired = true; 409 } 410 if (actionSet.size() > 1 && orRequired) { 411 orSeparatedActions.insert(0, "("); 412 orSeparatedActions.append(")"); 413 } 414 415 filter.setParameter(DagXLogInfoService.ACTION, orSeparatedActions.toString()); 416 if (actionSet != null && actionSet.size() == 1) { 417 CoordinatorActionBean actionBean = getCoordAction(actionSet.iterator().next()); 418 startTime = actionBean.getCreatedTime(); 419 endTime = actionBean.getStatus().equals(CoordinatorAction.Status.RUNNING) ? new Date() : actionBean 420 .getLastModifiedTime(); 421 filter.setActionList(true); 422 } 423 else if (actionSet != null && actionSet.size() > 0) { 424 List<String> tempList = new ArrayList<String>(actionSet); 425 Collections.sort(tempList, new Comparator<String>() { 426 public int compare(String a, String b) { 427 return Integer.valueOf(a.substring(a.lastIndexOf("@") + 1)).compareTo( 428 Integer.valueOf(b.substring(b.lastIndexOf("@") + 1))); 429 } 430 }); 431 startTime = getCoordAction(tempList.get(0)).getCreatedTime(); 432 endTime = CoordActionsInDateRange.getCoordActionsLastModifiedDate(jobId, tempList.get(0), 433 tempList.get(tempList.size() - 1)); 434 filter.setActionList(true); 435 } 436 } 437 // if coordinator action logs are to be retrieved based on date range 438 // this block gets the corresponding list of coordinator actions to be used by the log filter 439 if (logRetrievalType.equalsIgnoreCase(RestConstants.JOB_LOG_DATE)) { 440 List<String> coordActionIdList = null; 441 try { 442 coordActionIdList = CoordActionsInDateRange.getCoordActionIdsFromDates(jobId, logRetrievalScope); 443 } 444 catch (XException xe) { 445 throw new CommandException(ErrorCode.E0302, "Error in date range for coordinator actions", xe); 446 } 447 if(coordActionIdList.size() >= maxNumActionsForLog) { 448 throw new CommandException(ErrorCode.E0302, 449 "Retrieving log of too many coordinator actions. Max count is " 450 + maxNumActionsForLog + " actions"); 451 } 452 StringBuilder orSeparatedActions = new StringBuilder(""); 453 boolean orRequired = false; 454 for (String coordActionId : coordActionIdList) { 455 if (orRequired) { 456 orSeparatedActions.append("|"); 457 } 458 orSeparatedActions.append(coordActionId); 459 orRequired = true; 460 } 461 if (coordActionIdList.size() > 1 && orRequired) { 462 orSeparatedActions.insert(0, "("); 463 orSeparatedActions.append(")"); 464 } 465 filter.setParameter(DagXLogInfoService.ACTION, orSeparatedActions.toString()); 466 if (coordActionIdList != null && coordActionIdList.size() == 1) { 467 CoordinatorActionBean actionBean = getCoordAction(coordActionIdList.get(0)); 468 startTime = actionBean.getCreatedTime(); 469 endTime = actionBean.getStatus().equals(CoordinatorAction.Status.RUNNING) ? new Date() : actionBean 470 .getLastModifiedTime(); 471 filter.setActionList(true); 472 } 473 else if (coordActionIdList != null && coordActionIdList.size() > 0) { 474 Collections.sort(coordActionIdList, new Comparator<String>() { 475 public int compare(String a, String b) { 476 return Integer.valueOf(a.substring(a.lastIndexOf("@") + 1)).compareTo( 477 Integer.valueOf(b.substring(b.lastIndexOf("@") + 1))); 478 } 479 }); 480 startTime = getCoordAction(coordActionIdList.get(0)).getCreatedTime(); 481 endTime = CoordActionsInDateRange.getCoordActionsLastModifiedDate(jobId, coordActionIdList.get(0), 482 coordActionIdList.get(coordActionIdList.size() - 1)); 483 filter.setActionList(true); 484 } 485 } 486 } 487 if (startTime == null || endTime == null) { 488 CoordinatorJobBean job = getCoordJobWithNoActionInfo(jobId); 489 if (startTime == null) { 490 startTime = job.getCreatedTime(); 491 } 492 if (endTime == null) { 493 if (job.isTerminalStatus()) { 494 endTime = job.getLastModifiedTime(); 495 } 496 if (endTime == null) { 497 endTime = new Date(); 498 } 499 } 500 } 501 Services.get().get(XLogStreamingService.class).streamLog(filter, startTime, endTime, writer, params); 502 } 503 504 /* 505 * (non-Javadoc) 506 * 507 * @see 508 * org.apache.oozie.BaseEngine#submitJob(org.apache.hadoop.conf.Configuration 509 * , boolean) 510 */ 511 @Override 512 public String submitJob(Configuration conf, boolean startJob) throws CoordinatorEngineException { 513 try { 514 CoordSubmitXCommand submit = new CoordSubmitXCommand(conf); 515 return submit.call(); 516 } 517 catch (CommandException ex) { 518 throw new CoordinatorEngineException(ex); 519 } 520 } 521 522 /* 523 * (non-Javadoc) 524 * 525 * @see 526 * org.apache.oozie.BaseEngine#dryRunSubmit(org.apache.hadoop.conf.Configuration) 527 */ 528 @Override 529 public String dryRunSubmit(Configuration conf) throws CoordinatorEngineException { 530 try { 531 CoordSubmitXCommand submit = new CoordSubmitXCommand(true, conf); 532 return submit.call(); 533 } 534 catch (CommandException ex) { 535 throw new CoordinatorEngineException(ex); 536 } 537 } 538 539 /* 540 * (non-Javadoc) 541 * 542 * @see org.apache.oozie.BaseEngine#suspend(java.lang.String) 543 */ 544 @Override 545 public void suspend(String jobId) throws CoordinatorEngineException { 546 try { 547 new CoordSuspendXCommand(jobId).call(); 548 } 549 catch (CommandException e) { 550 throw new CoordinatorEngineException(e); 551 } 552 553 } 554 555 /* 556 * (non-Javadoc) 557 * 558 * @see org.apache.oozie.BaseEngine#getJob(java.lang.String) 559 */ 560 @Override 561 public WorkflowJob getJob(String jobId) throws BaseEngineException { 562 throw new BaseEngineException(new XException(ErrorCode.E0301, "cannot get a workflow job from CoordinatorEngine")); 563 } 564 565 /* 566 * (non-Javadoc) 567 * 568 * @see org.apache.oozie.BaseEngine#getJob(java.lang.String, int, int) 569 */ 570 @Override 571 public WorkflowJob getJob(String jobId, int start, int length) throws BaseEngineException { 572 throw new BaseEngineException(new XException(ErrorCode.E0301, "cannot get a workflow job from CoordinatorEngine")); 573 } 574 575 private static final Set<String> FILTER_NAMES = new HashSet<String>(); 576 577 static { 578 FILTER_NAMES.add(OozieClient.FILTER_USER); 579 FILTER_NAMES.add(OozieClient.FILTER_NAME); 580 FILTER_NAMES.add(OozieClient.FILTER_GROUP); 581 FILTER_NAMES.add(OozieClient.FILTER_STATUS); 582 FILTER_NAMES.add(OozieClient.FILTER_ID); 583 FILTER_NAMES.add(OozieClient.FILTER_FREQUENCY); 584 FILTER_NAMES.add(OozieClient.FILTER_UNIT); 585 } 586 587 /** 588 * @param filter 589 * @param start 590 * @param len 591 * @return CoordinatorJobInfo 592 * @throws CoordinatorEngineException 593 */ 594 public CoordinatorJobInfo getCoordJobs(String filter, int start, int len) throws CoordinatorEngineException { 595 Map<String, List<String>> filterList = parseJobsFilter(filter); 596 597 try { 598 return new CoordJobsXCommand(filterList, start, len).call(); 599 } 600 catch (CommandException ex) { 601 throw new CoordinatorEngineException(ex); 602 } 603 } 604 605 // Parses the filter string (e.g status=RUNNING;status=WAITING) and returns a list of status values 606 public Map<Pair<String, FILTER_COMPARATORS>, List<Object>> parseJobFilter(String filter) throws 607 CoordinatorEngineException { 608 Map<Pair<String, FILTER_COMPARATORS>, List<Object>> filterMap = new HashMap<Pair<String, 609 FILTER_COMPARATORS>, List<Object>>(); 610 if (filter != null) { 611 //split name value pairs 612 StringTokenizer st = new StringTokenizer(filter, ";"); 613 while (st.hasMoreTokens()) { 614 String token = st.nextToken().trim(); 615 Pair<String, FILTER_COMPARATORS> pair = null; 616 for (FILTER_COMPARATORS comp : FILTER_COMPARATORS.values()) { 617 if (token.contains(comp.getSign())) { 618 int index = token.indexOf(comp.getSign()); 619 String key = token.substring(0, index); 620 String valueStr = token.substring(index + comp.getSign().length()); 621 Object value; 622 623 if (key.equalsIgnoreCase(OozieClient.FILTER_STATUS)) { 624 value = valueStr.toUpperCase(); 625 try { 626 CoordinatorAction.Status.valueOf((String) value); 627 } catch (IllegalArgumentException ex) { 628 // Check for incorrect status value 629 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 630 XLog.format("invalid status value [{0}]." + " Valid status values are: [{1}]", 631 valueStr, StringUtils.join(CoordinatorAction.Status.values(), ", "))); 632 } 633 634 if (!(comp == FILTER_COMPARATORS.EQUALS || comp == FILTER_COMPARATORS.NOT_EQUALS)) { 635 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 636 XLog.format("invalid comparator [{0}] for status." + " Valid are = and !=", 637 comp.getSign())); 638 } 639 640 pair = Pair.of(OozieClient.FILTER_STATUS, comp); 641 } else if (key.equalsIgnoreCase(OozieClient.FILTER_NOMINAL_TIME)) { 642 try { 643 value = new Timestamp(DateUtils.parseDateUTC(valueStr).getTime()); 644 } catch (ParseException e) { 645 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 646 XLog.format("invalid nominal time [{0}]." + " Valid format: " + 647 "[{1}]", valueStr, DateUtils.ISO8601_UTC_MASK)); 648 } 649 pair = Pair.of(OozieClient.FILTER_NOMINAL_TIME, comp); 650 } else { 651 // Check for incorrect filter option 652 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 653 XLog.format("invalid filter [{0}]." + " Valid filters [{1}]", key, StringUtils.join 654 (VALID_JOB_FILTERS, ", "))); 655 } 656 if (!filterMap.containsKey(pair)) { 657 filterMap.put(pair, new ArrayList<Object>()); 658 } 659 filterMap.get(pair).add(value); 660 break; 661 } 662 } 663 664 if (pair == null) { 665 //token doesn't contain comparator 666 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 667 "filter should be of format <key><comparator><value> pairs"); 668 } 669 } 670 } 671 return filterMap; 672 } 673 674 /** 675 * @param filter 676 * @return Map<String, List<String>> 677 * @throws CoordinatorEngineException 678 */ 679 @VisibleForTesting 680 Map<String, List<String>> parseJobsFilter(String filter) throws CoordinatorEngineException { 681 Map<String, List<String>> map = new HashMap<String, List<String>>(); 682 boolean isTimeUnitSpecified = false; 683 String timeUnit = "MINUTE"; 684 boolean isFrequencySpecified = false; 685 String frequency = ""; 686 if (filter != null) { 687 StringTokenizer st = new StringTokenizer(filter, ";"); 688 while (st.hasMoreTokens()) { 689 String token = st.nextToken(); 690 if (token.contains("=")) { 691 String[] pair = token.split("="); 692 if (pair.length != 2) { 693 throw new CoordinatorEngineException(ErrorCode.E0420, filter, 694 "elements must be semicolon-separated name=value pairs"); 695 } 696 if (!FILTER_NAMES.contains(pair[0].toLowerCase())) { 697 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format("invalid name [{0}]", 698 pair[0])); 699 } 700 if (pair[0].equalsIgnoreCase("frequency")) { 701 isFrequencySpecified = true; 702 try { 703 frequency = (int) Float.parseFloat(pair[1]) + ""; 704 continue; 705 } 706 catch (NumberFormatException NANException) { 707 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format( 708 "invalid value [{0}] for frequency. A numerical value is expected", pair[1])); 709 } 710 } 711 if (pair[0].equalsIgnoreCase("unit")) { 712 isTimeUnitSpecified = true; 713 timeUnit = pair[1]; 714 if (!timeUnit.equalsIgnoreCase("months") && !timeUnit.equalsIgnoreCase("days") 715 && !timeUnit.equalsIgnoreCase("hours") && !timeUnit.equalsIgnoreCase("minutes")) { 716 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format( 717 "invalid value [{0}] for time unit. " 718 + "Valid value is one of months, days, hours or minutes", pair[1])); 719 } 720 continue; 721 } 722 if (pair[0].equals("status")) { 723 try { 724 CoordinatorJob.Status.valueOf(pair[1]); 725 } 726 catch (IllegalArgumentException ex) { 727 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format( 728 "invalid status [{0}]", pair[1])); 729 } 730 } 731 List<String> list = map.get(pair[0]); 732 if (list == null) { 733 list = new ArrayList<String>(); 734 map.put(pair[0], list); 735 } 736 list.add(pair[1]); 737 } else { 738 throw new CoordinatorEngineException(ErrorCode.E0420, filter, 739 "elements must be semicolon-separated name=value pairs"); 740 } 741 } 742 // Unit is specified and frequency is not specified 743 if (!isFrequencySpecified && isTimeUnitSpecified) { 744 throw new CoordinatorEngineException(ErrorCode.E0420, filter, "time unit should be added only when " 745 + "frequency is specified. Either specify frequency also or else remove the time unit"); 746 } else if (isFrequencySpecified) { 747 // Frequency value is specified 748 if (isTimeUnitSpecified) { 749 if (timeUnit.equalsIgnoreCase("months")) { 750 timeUnit = "MONTH"; 751 } else if (timeUnit.equalsIgnoreCase("days")) { 752 timeUnit = "DAY"; 753 } else if (timeUnit.equalsIgnoreCase("hours")) { 754 // When job details are persisted to database, frequency in hours are converted to minutes. 755 // This conversion is to conform with that. 756 frequency = Integer.parseInt(frequency) * 60 + ""; 757 timeUnit = "MINUTE"; 758 } else if (timeUnit.equalsIgnoreCase("minutes")) { 759 timeUnit = "MINUTE"; 760 } 761 } 762 // Adding the frequency and time unit filters to the filter map 763 List<String> list = new ArrayList<String>(); 764 list.add(timeUnit); 765 map.put("unit", list); 766 list = new ArrayList<String>(); 767 list.add(frequency); 768 map.put("frequency", list); 769 } 770 } 771 return map; 772 } 773 774 public List<WorkflowJobBean> getReruns(String coordActionId) throws CoordinatorEngineException { 775 List<WorkflowJobBean> wfBeans; 776 try { 777 wfBeans = WorkflowJobQueryExecutor.getInstance().getList(WorkflowJobQuery.GET_WORKFLOWS_PARENT_COORD_RERUN, 778 coordActionId); 779 } 780 catch (JPAExecutorException e) { 781 throw new CoordinatorEngineException(e); 782 } 783 return wfBeans; 784 } 785 786 /** 787 * Update coord job definition. 788 * 789 * @param conf the conf 790 * @param jobId the job id 791 * @param dryrun the dryrun 792 * @param showDiff the show diff 793 * @return the string 794 * @throws CoordinatorEngineException the coordinator engine exception 795 */ 796 public String updateJob(Configuration conf, String jobId, boolean dryrun, boolean showDiff) 797 throws CoordinatorEngineException { 798 try { 799 CoordUpdateXCommand update = new CoordUpdateXCommand(dryrun, conf, jobId, showDiff); 800 return update.call(); 801 } 802 catch (CommandException ex) { 803 throw new CoordinatorEngineException(ex); 804 } 805 } 806 807 /** 808 * Return the status for a Job ID 809 * 810 * @param jobId job Id. 811 * @return the job's status 812 * @throws CoordinatorEngineException thrown if the job's status could not be obtained 813 */ 814 @Override 815 public String getJobStatus(String jobId) throws CoordinatorEngineException { 816 try { 817 CoordinatorJobBean coordJob = CoordJobQueryExecutor.getInstance().get( 818 CoordJobQueryExecutor.CoordJobQuery.GET_COORD_JOB_STATUS, jobId); 819 return coordJob.getStatusStr(); 820 } 821 catch (JPAExecutorException e) { 822 throw new CoordinatorEngineException(e); 823 } 824 } 825 826 /** 827 * Return the status for an Action ID 828 * 829 * @param actionId action Id. 830 * @return the action's status 831 * @throws CoordinatorEngineException thrown if the action's status could not be obtained 832 */ 833 public String getActionStatus(String actionId) throws CoordinatorEngineException { 834 try { 835 CoordinatorActionBean coordAction = CoordActionQueryExecutor.getInstance().get( 836 CoordActionQueryExecutor.CoordActionQuery.GET_COORD_ACTION_STATUS, actionId); 837 return coordAction.getStatusStr(); 838 } 839 catch (JPAExecutorException e) { 840 throw new CoordinatorEngineException(e); 841 } 842 } 843 844 /** 845 * return a list of killed Coordinator job 846 * 847 * @param filter, the filter string for which the coordinator jobs are killed 848 * @param start, the starting index for coordinator jobs 849 * @param length, maximum number of jobs to be killed 850 * @return the list of jobs being killed 851 * @throws CoordinatorEngineException thrown if one or more of the jobs cannot be killed 852 */ 853 public CoordinatorJobInfo killJobs(String filter, int start, int length) throws CoordinatorEngineException { 854 try { 855 Map<String, List<String>> filterMap = parseJobsFilter(filter); 856 CoordinatorJobInfo coordinatorJobInfo = 857 new BulkCoordXCommand(filterMap, start, length, OperationType.Kill).call(); 858 if (coordinatorJobInfo == null) { 859 return new CoordinatorJobInfo(new ArrayList<CoordinatorJobBean>(), 0, 0, 0); 860 } 861 return coordinatorJobInfo; 862 } 863 catch (CommandException ex) { 864 throw new CoordinatorEngineException(ex); 865 } 866 } 867 868 /** 869 * return the jobs that've been suspended 870 * @param filter Filter for jobs that will be suspended, can be name, user, group, status, id or combination of any 871 * @param start Offset for the jobs that will be suspended 872 * @param length maximum number of jobs that will be suspended 873 * @return 874 * @throws CoordinatorEngineException 875 */ 876 public CoordinatorJobInfo suspendJobs(String filter, int start, int length) throws CoordinatorEngineException { 877 try { 878 Map<String, List<String>> filterMap = parseJobsFilter(filter); 879 CoordinatorJobInfo coordinatorJobInfo = 880 new BulkCoordXCommand(filterMap, start, length, OperationType.Suspend).call(); 881 if (coordinatorJobInfo == null) { 882 return new CoordinatorJobInfo(new ArrayList<CoordinatorJobBean>(), 0, 0, 0); 883 } 884 return coordinatorJobInfo; 885 } 886 catch (CommandException ex) { 887 throw new CoordinatorEngineException(ex); 888 } 889 } 890 891 /** 892 * return the jobs that've been resumed 893 * @param filter Filter for jobs that will be resumed, can be name, user, group, status, id or combination of any 894 * @param start Offset for the jobs that will be resumed 895 * @param length maximum number of jobs that will be resumed 896 * @return 897 * @throws CoordinatorEngineException 898 */ 899 public CoordinatorJobInfo resumeJobs(String filter, int start, int length) throws CoordinatorEngineException { 900 try { 901 Map<String, List<String>> filterMap = parseJobsFilter(filter); 902 CoordinatorJobInfo coordinatorJobInfo = 903 new BulkCoordXCommand(filterMap, start, length, OperationType.Resume).call(); 904 if (coordinatorJobInfo == null) { 905 return new CoordinatorJobInfo(new ArrayList<CoordinatorJobBean>(), 0, 0, 0); 906 } 907 return coordinatorJobInfo; 908 } 909 catch (CommandException ex) { 910 throw new CoordinatorEngineException(ex); 911 } 912 } 913}