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 FILTER_NAMES.add(OozieClient.FILTER_SORT_BY); 586 FILTER_NAMES.add(OozieClient.FILTER_CREATED_TIME_START); 587 FILTER_NAMES.add(OozieClient.FILTER_CREATED_TIME_END); 588 FILTER_NAMES.add(OozieClient.FILTER_TEXT); 589 } 590 591 /** 592 * @param filter 593 * @param start 594 * @param len 595 * @return CoordinatorJobInfo 596 * @throws CoordinatorEngineException 597 */ 598 public CoordinatorJobInfo getCoordJobs(String filter, int start, int len) throws CoordinatorEngineException { 599 Map<String, List<String>> filterList = parseJobsFilter(filter); 600 601 try { 602 return new CoordJobsXCommand(filterList, start, len).call(); 603 } 604 catch (CommandException ex) { 605 throw new CoordinatorEngineException(ex); 606 } 607 } 608 609 // Parses the filter string (e.g status=RUNNING;status=WAITING) and returns a list of status values 610 public Map<Pair<String, FILTER_COMPARATORS>, List<Object>> parseJobFilter(String filter) throws 611 CoordinatorEngineException { 612 Map<Pair<String, FILTER_COMPARATORS>, List<Object>> filterMap = new HashMap<Pair<String, 613 FILTER_COMPARATORS>, List<Object>>(); 614 if (filter != null) { 615 //split name value pairs 616 StringTokenizer st = new StringTokenizer(filter, ";"); 617 while (st.hasMoreTokens()) { 618 String token = st.nextToken().trim(); 619 Pair<String, FILTER_COMPARATORS> pair = null; 620 for (FILTER_COMPARATORS comp : FILTER_COMPARATORS.values()) { 621 if (token.contains(comp.getSign())) { 622 int index = token.indexOf(comp.getSign()); 623 String key = token.substring(0, index); 624 String valueStr = token.substring(index + comp.getSign().length()); 625 Object value; 626 627 if (key.equalsIgnoreCase(OozieClient.FILTER_STATUS)) { 628 value = valueStr.toUpperCase(); 629 try { 630 CoordinatorAction.Status.valueOf((String) value); 631 } catch (IllegalArgumentException ex) { 632 // Check for incorrect status value 633 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 634 XLog.format("invalid status value [{0}]." + " Valid status values are: [{1}]", 635 valueStr, StringUtils.join(CoordinatorAction.Status.values(), ", "))); 636 } 637 638 if (!(comp == FILTER_COMPARATORS.EQUALS || comp == FILTER_COMPARATORS.NOT_EQUALS)) { 639 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 640 XLog.format("invalid comparator [{0}] for status." + " Valid are = and !=", 641 comp.getSign())); 642 } 643 644 pair = Pair.of(OozieClient.FILTER_STATUS, comp); 645 } else if (key.equalsIgnoreCase(OozieClient.FILTER_NOMINAL_TIME)) { 646 try { 647 value = new Timestamp(DateUtils.parseDateUTC(valueStr).getTime()); 648 } catch (ParseException e) { 649 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 650 XLog.format("invalid nominal time [{0}]." + " Valid format: " + 651 "[{1}]", valueStr, DateUtils.ISO8601_UTC_MASK)); 652 } 653 pair = Pair.of(OozieClient.FILTER_NOMINAL_TIME, comp); 654 } else { 655 // Check for incorrect filter option 656 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 657 XLog.format("invalid filter [{0}]." + " Valid filters [{1}]", key, StringUtils.join 658 (VALID_JOB_FILTERS, ", "))); 659 } 660 if (!filterMap.containsKey(pair)) { 661 filterMap.put(pair, new ArrayList<Object>()); 662 } 663 filterMap.get(pair).add(value); 664 break; 665 } 666 } 667 668 if (pair == null) { 669 //token doesn't contain comparator 670 throw new CoordinatorEngineException(ErrorCode.E0421, filter, 671 "filter should be of format <key><comparator><value> pairs"); 672 } 673 } 674 } 675 return filterMap; 676 } 677 678 /** 679 * @param filter 680 * @return Map<String, List<String>> 681 * @throws CoordinatorEngineException 682 */ 683 @VisibleForTesting 684 Map<String, List<String>> parseJobsFilter(String filter) throws CoordinatorEngineException { 685 Map<String, List<String>> map = new HashMap<String, List<String>>(); 686 boolean isTimeUnitSpecified = false; 687 String timeUnit = "MINUTE"; 688 boolean isFrequencySpecified = false; 689 String frequency = ""; 690 if (filter != null) { 691 StringTokenizer st = new StringTokenizer(filter, ";"); 692 while (st.hasMoreTokens()) { 693 String token = st.nextToken(); 694 if (token.contains("=")) { 695 String[] pair = token.split("="); 696 if (pair.length != 2) { 697 throw new CoordinatorEngineException(ErrorCode.E0420, filter, 698 "elements must be semicolon-separated name=value pairs"); 699 } 700 pair[0] = pair[0].toLowerCase(); 701 if (!FILTER_NAMES.contains(pair[0])) { 702 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format("invalid name [{0}]", 703 pair[0])); 704 } 705 if (pair[0].equalsIgnoreCase("frequency")) { 706 isFrequencySpecified = true; 707 try { 708 frequency = (int) Float.parseFloat(pair[1]) + ""; 709 continue; 710 } 711 catch (NumberFormatException NANException) { 712 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format( 713 "invalid value [{0}] for frequency. A numerical value is expected", pair[1])); 714 } 715 } 716 if (pair[0].equalsIgnoreCase("unit")) { 717 isTimeUnitSpecified = true; 718 timeUnit = pair[1]; 719 if (!timeUnit.equalsIgnoreCase("months") && !timeUnit.equalsIgnoreCase("days") 720 && !timeUnit.equalsIgnoreCase("hours") && !timeUnit.equalsIgnoreCase("minutes")) { 721 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format( 722 "invalid value [{0}] for time unit. " 723 + "Valid value is one of months, days, hours or minutes", pair[1])); 724 } 725 continue; 726 } 727 if (pair[0].equals("status")) { 728 try { 729 CoordinatorJob.Status.valueOf(pair[1]); 730 } 731 catch (IllegalArgumentException ex) { 732 throw new CoordinatorEngineException(ErrorCode.E0420, filter, XLog.format( 733 "invalid status [{0}]", pair[1])); 734 } 735 } 736 List<String> list = map.get(pair[0]); 737 if (list == null) { 738 list = new ArrayList<String>(); 739 map.put(pair[0], list); 740 } 741 list.add(pair[1]); 742 } else { 743 throw new CoordinatorEngineException(ErrorCode.E0420, filter, 744 "elements must be semicolon-separated name=value pairs"); 745 } 746 } 747 // Unit is specified and frequency is not specified 748 if (!isFrequencySpecified && isTimeUnitSpecified) { 749 throw new CoordinatorEngineException(ErrorCode.E0420, filter, "time unit should be added only when " 750 + "frequency is specified. Either specify frequency also or else remove the time unit"); 751 } else if (isFrequencySpecified) { 752 // Frequency value is specified 753 if (isTimeUnitSpecified) { 754 if (timeUnit.equalsIgnoreCase("months")) { 755 timeUnit = "MONTH"; 756 } else if (timeUnit.equalsIgnoreCase("days")) { 757 timeUnit = "DAY"; 758 } else if (timeUnit.equalsIgnoreCase("hours")) { 759 // When job details are persisted to database, frequency in hours are converted to minutes. 760 // This conversion is to conform with that. 761 frequency = Integer.parseInt(frequency) * 60 + ""; 762 timeUnit = "MINUTE"; 763 } else if (timeUnit.equalsIgnoreCase("minutes")) { 764 timeUnit = "MINUTE"; 765 } 766 } 767 // Adding the frequency and time unit filters to the filter map 768 List<String> list = new ArrayList<String>(); 769 list.add(timeUnit); 770 map.put("unit", list); 771 list = new ArrayList<String>(); 772 list.add(frequency); 773 map.put("frequency", list); 774 } 775 } 776 return map; 777 } 778 779 public List<WorkflowJobBean> getReruns(String coordActionId) throws CoordinatorEngineException { 780 List<WorkflowJobBean> wfBeans; 781 try { 782 wfBeans = WorkflowJobQueryExecutor.getInstance().getList(WorkflowJobQuery.GET_WORKFLOWS_PARENT_COORD_RERUN, 783 coordActionId); 784 } 785 catch (JPAExecutorException e) { 786 throw new CoordinatorEngineException(e); 787 } 788 return wfBeans; 789 } 790 791 /** 792 * Update coord job definition. 793 * 794 * @param conf the conf 795 * @param jobId the job id 796 * @param dryrun the dryrun 797 * @param showDiff the show diff 798 * @return the string 799 * @throws CoordinatorEngineException the coordinator engine exception 800 */ 801 public String updateJob(Configuration conf, String jobId, boolean dryrun, boolean showDiff) 802 throws CoordinatorEngineException { 803 try { 804 CoordUpdateXCommand update = new CoordUpdateXCommand(dryrun, conf, jobId, showDiff); 805 return update.call(); 806 } 807 catch (CommandException ex) { 808 throw new CoordinatorEngineException(ex); 809 } 810 } 811 812 /** 813 * Return the status for a Job ID 814 * 815 * @param jobId job Id. 816 * @return the job's status 817 * @throws CoordinatorEngineException thrown if the job's status could not be obtained 818 */ 819 @Override 820 public String getJobStatus(String jobId) throws CoordinatorEngineException { 821 try { 822 CoordinatorJobBean coordJob = CoordJobQueryExecutor.getInstance().get( 823 CoordJobQueryExecutor.CoordJobQuery.GET_COORD_JOB_STATUS, jobId); 824 return coordJob.getStatusStr(); 825 } 826 catch (JPAExecutorException e) { 827 throw new CoordinatorEngineException(e); 828 } 829 } 830 831 /** 832 * Return the status for an Action ID 833 * 834 * @param actionId action Id. 835 * @return the action's status 836 * @throws CoordinatorEngineException thrown if the action's status could not be obtained 837 */ 838 public String getActionStatus(String actionId) throws CoordinatorEngineException { 839 try { 840 CoordinatorActionBean coordAction = CoordActionQueryExecutor.getInstance().get( 841 CoordActionQueryExecutor.CoordActionQuery.GET_COORD_ACTION_STATUS, actionId); 842 return coordAction.getStatusStr(); 843 } 844 catch (JPAExecutorException e) { 845 throw new CoordinatorEngineException(e); 846 } 847 } 848 849 /** 850 * return a list of killed Coordinator job 851 * 852 * @param filter, the filter string for which the coordinator jobs are killed 853 * @param start, the starting index for coordinator jobs 854 * @param length, maximum number of jobs to be killed 855 * @return the list of jobs being killed 856 * @throws CoordinatorEngineException thrown if one or more of the jobs cannot be killed 857 */ 858 public CoordinatorJobInfo killJobs(String filter, int start, int length) throws CoordinatorEngineException { 859 try { 860 Map<String, List<String>> filterMap = parseJobsFilter(filter); 861 CoordinatorJobInfo coordinatorJobInfo = 862 new BulkCoordXCommand(filterMap, start, length, OperationType.Kill).call(); 863 if (coordinatorJobInfo == null) { 864 return new CoordinatorJobInfo(new ArrayList<CoordinatorJobBean>(), 0, 0, 0); 865 } 866 return coordinatorJobInfo; 867 } 868 catch (CommandException ex) { 869 throw new CoordinatorEngineException(ex); 870 } 871 } 872 873 /** 874 * return the jobs that've been suspended 875 * @param filter Filter for jobs that will be suspended, can be name, user, group, status, id or combination of any 876 * @param start Offset for the jobs that will be suspended 877 * @param length maximum number of jobs that will be suspended 878 * @return 879 * @throws CoordinatorEngineException 880 */ 881 public CoordinatorJobInfo suspendJobs(String filter, int start, int length) throws CoordinatorEngineException { 882 try { 883 Map<String, List<String>> filterMap = parseJobsFilter(filter); 884 CoordinatorJobInfo coordinatorJobInfo = 885 new BulkCoordXCommand(filterMap, start, length, OperationType.Suspend).call(); 886 if (coordinatorJobInfo == null) { 887 return new CoordinatorJobInfo(new ArrayList<CoordinatorJobBean>(), 0, 0, 0); 888 } 889 return coordinatorJobInfo; 890 } 891 catch (CommandException ex) { 892 throw new CoordinatorEngineException(ex); 893 } 894 } 895 896 /** 897 * return the jobs that've been resumed 898 * @param filter Filter for jobs that will be resumed, can be name, user, group, status, id or combination of any 899 * @param start Offset for the jobs that will be resumed 900 * @param length maximum number of jobs that will be resumed 901 * @return 902 * @throws CoordinatorEngineException 903 */ 904 public CoordinatorJobInfo resumeJobs(String filter, int start, int length) throws CoordinatorEngineException { 905 try { 906 Map<String, List<String>> filterMap = parseJobsFilter(filter); 907 CoordinatorJobInfo coordinatorJobInfo = 908 new BulkCoordXCommand(filterMap, start, length, OperationType.Resume).call(); 909 if (coordinatorJobInfo == null) { 910 return new CoordinatorJobInfo(new ArrayList<CoordinatorJobBean>(), 0, 0, 0); 911 } 912 return coordinatorJobInfo; 913 } 914 catch (CommandException ex) { 915 throw new CoordinatorEngineException(ex); 916 } 917 } 918}