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.coord; 020 021import java.text.ParseException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Date; 025import java.util.HashSet; 026import java.util.LinkedHashSet; 027import java.util.List; 028import java.util.Set; 029import java.util.Map; 030import java.util.HashMap; 031import java.util.concurrent.TimeUnit; 032 033import org.apache.commons.lang.StringUtils; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.oozie.CoordinatorActionBean; 036import org.apache.oozie.CoordinatorEngine; 037import org.apache.oozie.ErrorCode; 038import org.apache.oozie.XException; 039import org.apache.oozie.client.OozieClient; 040import org.apache.oozie.client.rest.RestConstants; 041import org.apache.oozie.command.CommandException; 042import org.apache.oozie.coord.input.logic.CoordInputLogicEvaluator; 043import org.apache.oozie.coord.input.logic.InputLogicParser; 044import org.apache.oozie.executor.jpa.CoordActionGetJPAExecutor; 045import org.apache.oozie.executor.jpa.CoordJobGetActionForNominalTimeJPAExecutor; 046import org.apache.oozie.executor.jpa.JPAExecutorException; 047import org.apache.oozie.service.ConfigurationService; 048import org.apache.oozie.service.JPAService; 049import org.apache.oozie.service.Services; 050import org.apache.oozie.service.XLogService; 051import org.apache.oozie.sla.SLAOperations; 052import org.apache.oozie.util.CoordActionsInDateRange; 053import org.apache.oozie.util.DateUtils; 054import org.apache.oozie.util.Pair; 055import org.apache.oozie.util.ParamChecker; 056import org.apache.oozie.util.XLog; 057import org.apache.oozie.util.XmlUtils; 058import org.jdom.Element; 059import org.jdom.JDOMException; 060 061import com.google.common.annotations.VisibleForTesting; 062 063 064public class CoordUtils { 065 public static final String HADOOP_USER = "user.name"; 066 067 public static String getDoneFlag(Element doneFlagElement) { 068 if (doneFlagElement != null) { 069 return doneFlagElement.getTextTrim(); 070 } 071 else { 072 return CoordELConstants.DEFAULT_DONE_FLAG; 073 } 074 } 075 076 public static Configuration getHadoopConf(Configuration jobConf) { 077 Configuration conf = new Configuration(); 078 ParamChecker.notNull(jobConf, "Configuration to be used for hadoop setup "); 079 String user = ParamChecker.notEmpty(jobConf.get(OozieClient.USER_NAME), OozieClient.USER_NAME); 080 conf.set(HADOOP_USER, user); 081 return conf; 082 } 083 084 /** 085 * Get the list of actions for a given coordinator job 086 * @param rangeType the rerun type (date, action) 087 * @param jobId the coordinator job id 088 * @param scope the date scope or action id scope 089 * @return the list of Coordinator actions 090 * @throws CommandException 091 */ 092 public static List<CoordinatorActionBean> getCoordActions(String rangeType, String jobId, String scope, 093 boolean active) throws CommandException { 094 List<CoordinatorActionBean> coordActions = null; 095 if (rangeType.equals(RestConstants.JOB_COORD_SCOPE_DATE)) { 096 coordActions = CoordUtils.getCoordActionsFromDates(jobId, scope, active); 097 } 098 else if (rangeType.equals(RestConstants.JOB_COORD_SCOPE_ACTION)) { 099 coordActions = CoordUtils.getCoordActionsFromIds(jobId, scope); 100 } 101 return coordActions; 102 } 103 104 public static List<String> getActionListForScopeAndDate(String id, String scope, String dates) throws CommandException { 105 List<String> actionIds = new ArrayList<String>(); 106 107 List<String> parsed = new ArrayList<String>(); 108 if (scope == null && dates == null) { 109 parsed.add(id); 110 return parsed; 111 } 112 113 if (dates != null) { 114 List<CoordinatorActionBean> actionSet = CoordUtils.getCoordActionsFromDates(id, dates, true); 115 for (CoordinatorActionBean action : actionSet) { 116 actionIds.add(action.getId()); 117 } 118 parsed.addAll(actionIds); 119 } 120 if (scope != null) { 121 parsed.addAll(CoordUtils.getActionsIds(id, scope)); 122 } 123 return parsed; 124 } 125 126 127 /** 128 * Get the list of actions for given date ranges 129 * 130 * @param jobId coordinator job id 131 * @param scope a comma-separated list of date ranges. Each date range element is specified with two dates separated by '::' 132 * @return the list of Coordinator actions for the date range 133 * @throws CommandException thrown if failed to get coordinator actions by given date range 134 */ 135 @VisibleForTesting 136 public static List<CoordinatorActionBean> getCoordActionsFromDates(String jobId, String scope, boolean active) 137 throws CommandException { 138 JPAService jpaService = Services.get().get(JPAService.class); 139 ParamChecker.notEmpty(jobId, "jobId"); 140 ParamChecker.notEmpty(scope, "scope"); 141 142 Set<CoordinatorActionBean> actionSet = new LinkedHashSet<CoordinatorActionBean>(); 143 String[] list = scope.split(","); 144 for (String s : list) { 145 s = s.trim(); 146 // A date range is specified with two dates separated by '::' 147 if (s.contains("::")) { 148 List<CoordinatorActionBean> listOfActions; 149 try { 150 // Get list of actions within the range of date 151 listOfActions = CoordActionsInDateRange.getCoordActionsFromDateRange(jobId, s, active); 152 } 153 catch (XException e) { 154 throw new CommandException(e); 155 } 156 actionSet.addAll(listOfActions); 157 } 158 else { 159 try { 160 // Get action for the nominal time 161 Date date = DateUtils.parseDateOozieTZ(s.trim()); 162 CoordinatorActionBean coordAction = jpaService 163 .execute(new CoordJobGetActionForNominalTimeJPAExecutor(jobId, date)); 164 165 if (coordAction != null) { 166 actionSet.add(coordAction); 167 } 168 else { 169 throw new RuntimeException("This should never happen, Coordinator Action shouldn't be null"); 170 } 171 } 172 catch (ParseException e) { 173 throw new CommandException(ErrorCode.E0302, s.trim(), e); 174 } 175 catch (JPAExecutorException e) { 176 if (e.getErrorCode() == ErrorCode.E0605) { 177 XLog.getLog(CoordUtils.class).info("No action for nominal time:" + s + ". Skipping over"); 178 } 179 throw new CommandException(e); 180 } 181 182 } 183 } 184 185 List<CoordinatorActionBean> coordActions = new ArrayList<CoordinatorActionBean>(); 186 for (CoordinatorActionBean coordAction : actionSet) { 187 coordActions.add(coordAction); 188 } 189 return coordActions; 190 } 191 192 public static Set<String> getActionsIds(String jobId, String scope) throws CommandException { 193 ParamChecker.notEmpty(jobId, "jobId"); 194 ParamChecker.notEmpty(scope, "scope"); 195 196 Set<String> actions = new LinkedHashSet<String>(); 197 String[] list = scope.split(","); 198 for (String s : list) { 199 s = s.trim(); 200 // An action range is specified with two actions separated by '-' 201 if (s.contains("-")) { 202 String[] range = s.split("-"); 203 // Check the format for action's range 204 if (range.length != 2) { 205 throw new CommandException(ErrorCode.E0302, "format is wrong for action's range '" + s + "', an example of" 206 + " correct format is 1-5"); 207 } 208 int start; 209 int end; 210 //Get the starting and ending action numbers 211 try { 212 start = Integer.parseInt(range[0].trim()); 213 } catch (NumberFormatException ne) { 214 throw new CommandException(ErrorCode.E0302, "could not parse " + range[0].trim() + "into an integer", ne); 215 } 216 try { 217 end = Integer.parseInt(range[1].trim()); 218 } catch (NumberFormatException ne) { 219 throw new CommandException(ErrorCode.E0302, "could not parse " + range[1].trim() + "into an integer", ne); 220 } 221 if (start > end) { 222 throw new CommandException(ErrorCode.E0302, "format is wrong for action's range '" + s + "', starting action" 223 + "number of the range should be less than ending action number, an example will be 1-4"); 224 } 225 // Add the actionIds 226 for (int i = start; i <= end; i++) { 227 actions.add(jobId + "@" + i); 228 } 229 } 230 else { 231 try { 232 Integer.parseInt(s); 233 } 234 catch (NumberFormatException ne) { 235 throw new CommandException(ErrorCode.E0302, "format is wrong for action id'" + s 236 + "'. Integer only."); 237 } 238 actions.add(jobId + "@" + s); 239 } 240 } 241 return actions; 242 } 243 244 /** 245 * Get the list of actions for given id ranges 246 * 247 * @param jobId coordinator job id 248 * @param scope a comma-separated list of action ranges. The action range is specified with two action numbers separated by '-' 249 * @return the list of all Coordinator actions for action range 250 * @throws CommandException thrown if failed to get coordinator actions by given id range 251 */ 252 @VisibleForTesting 253 public static List<CoordinatorActionBean> getCoordActionsFromIds(String jobId, String scope) throws CommandException { 254 JPAService jpaService = Services.get().get(JPAService.class); 255 Set<String> actions = getActionsIds(jobId, scope); 256 // Retrieve the actions using the corresponding actionIds 257 List<CoordinatorActionBean> coordActions = new ArrayList<CoordinatorActionBean>(); 258 for (String id : actions) { 259 CoordinatorActionBean coordAction = null; 260 try { 261 coordAction = jpaService.execute(new CoordActionGetJPAExecutor(id)); 262 } 263 catch (JPAExecutorException je) { 264 if (je.getErrorCode().equals(ErrorCode.E0605)) { //ignore retrieval of non-existent actions in range 265 XLog.getLog(XLogService.class).warn( 266 "Coord action ID num [{0}] not yet materialized. Hence skipping over it for Kill action", 267 id.substring(id.indexOf("@") + 1)); 268 continue; 269 } 270 else { 271 throw new CommandException(je); 272 } 273 } 274 coordActions.add(coordAction); 275 } 276 return coordActions; 277 } 278 279 /** 280 * Check if sla alert is disabled for action. 281 * @param actionBean 282 * @param coordName 283 * @param jobConf 284 * @return true if SLA alert is disabled for action 285 * @throws ParseException 286 */ 287 public static boolean isSlaAlertDisabled(CoordinatorActionBean actionBean, String coordName, Configuration jobConf) 288 throws ParseException { 289 290 int disableSlaNotificationOlderThan = jobConf.getInt(OozieClient.SLA_DISABLE_ALERT_OLDER_THAN, 291 ConfigurationService.getInt(OozieClient.SLA_DISABLE_ALERT_OLDER_THAN)); 292 293 if (disableSlaNotificationOlderThan > 0) { 294 // Disable alert for catchup jobs 295 long timeDiffinHrs = TimeUnit.MILLISECONDS.toHours(new Date().getTime() 296 - actionBean.getNominalTime().getTime()); 297 if (timeDiffinHrs > jobConf.getLong(OozieClient.SLA_DISABLE_ALERT_OLDER_THAN, 298 ConfigurationService.getLong(OozieClient.SLA_DISABLE_ALERT_OLDER_THAN))) { 299 return true; 300 } 301 } 302 303 boolean disableAlert = false; 304 if (jobConf.get(OozieClient.SLA_DISABLE_ALERT_COORD) != null) { 305 String coords = jobConf.get(OozieClient.SLA_DISABLE_ALERT_COORD); 306 Set<String> coordsToDisableFor = new HashSet<String>(Arrays.asList(coords.split(","))); 307 if (coordsToDisableFor.contains(coordName)) { 308 return true; 309 } 310 if (coordsToDisableFor.contains(actionBean.getJobId())) { 311 return true; 312 } 313 } 314 315 // Check if sla alert is disabled for that action 316 if (!StringUtils.isEmpty(jobConf.get(OozieClient.SLA_DISABLE_ALERT)) 317 && getCoordActionSLAAlertStatus(actionBean, coordName, jobConf, OozieClient.SLA_DISABLE_ALERT)) { 318 return true; 319 } 320 321 // Check if sla alert is enabled for that action 322 if (!StringUtils.isEmpty(jobConf.get(OozieClient.SLA_ENABLE_ALERT)) 323 && getCoordActionSLAAlertStatus(actionBean, coordName, jobConf, OozieClient.SLA_ENABLE_ALERT)) { 324 return false; 325 } 326 327 return disableAlert; 328 } 329 330 /** 331 * Get coord action SLA alert status. 332 * @param actionBean 333 * @param coordName 334 * @param jobConf 335 * @param slaAlertType 336 * @return status of coord action SLA alert 337 * @throws ParseException 338 */ 339 private static boolean getCoordActionSLAAlertStatus(CoordinatorActionBean actionBean, String coordName, 340 Configuration jobConf, String slaAlertType) throws ParseException { 341 String slaAlertList; 342 343 if (!StringUtils.isEmpty(jobConf.get(slaAlertType))) { 344 slaAlertList = jobConf.get(slaAlertType); 345 // check if ALL or date/action-num range 346 if (slaAlertList.equalsIgnoreCase(SLAOperations.ALL_VALUE)) { 347 return true; 348 } 349 String[] values = slaAlertList.split(","); 350 for (String value : values) { 351 value = value.trim(); 352 if (value.contains("::")) { 353 String[] datesInRange = value.split("::"); 354 Date start = DateUtils.parseDateOozieTZ(datesInRange[0].trim()); 355 Date end = DateUtils.parseDateOozieTZ(datesInRange[1].trim()); 356 // check if nominal time in this range 357 if (actionBean.getNominalTime().compareTo(start) >= 0 358 || actionBean.getNominalTime().compareTo(end) <= 0) { 359 return true; 360 } 361 } 362 else if (value.contains("-")) { 363 String[] actionsInRange = value.split("-"); 364 int start = Integer.parseInt(actionsInRange[0].trim()); 365 int end = Integer.parseInt(actionsInRange[1].trim()); 366 // check if action number in this range 367 if (actionBean.getActionNumber() >= start || actionBean.getActionNumber() <= end) { 368 return true; 369 } 370 } 371 else { 372 int actionNumber = Integer.parseInt(value.trim()); 373 if (actionBean.getActionNumber() == actionNumber) { 374 return true; 375 } 376 } 377 } 378 } 379 return false; 380 } 381 382 // Form the where clause to filter by status values 383 public static Map<String, Object> getWhereClause(StringBuilder sb, Map<Pair<String, CoordinatorEngine.FILTER_COMPARATORS>, 384 List<Object>> filterMap) { 385 Map<String, Object> params = new HashMap<String, Object>(); 386 int pcnt= 1; 387 for (Map.Entry<Pair<String, CoordinatorEngine.FILTER_COMPARATORS>, List<Object>> filter : filterMap.entrySet()) { 388 String field = filter.getKey().getFirst(); 389 CoordinatorEngine.FILTER_COMPARATORS comp = filter.getKey().getSecond(); 390 String sqlField; 391 if (field.equals(OozieClient.FILTER_STATUS)) { 392 sqlField = "a.statusStr"; 393 } else if (field.equals(OozieClient.FILTER_NOMINAL_TIME)) { 394 sqlField = "a.nominalTimestamp"; 395 } else { 396 throw new IllegalArgumentException("Invalid filter key " + field); 397 } 398 399 sb.append(" and ").append(sqlField).append(" "); 400 switch (comp) { 401 case EQUALS: 402 sb.append("IN ("); 403 params.putAll(appendParams(sb, filter.getValue(), pcnt)); 404 sb.append(")"); 405 break; 406 407 case NOT_EQUALS: 408 sb.append("NOT IN ("); 409 params.putAll(appendParams(sb, filter.getValue(), pcnt)); 410 sb.append(")"); 411 break; 412 413 case GREATER: 414 case GREATER_EQUAL: 415 case LESSTHAN: 416 case LESSTHAN_EQUAL: 417 if (filter.getValue().size() != 1) { 418 throw new IllegalArgumentException(field + comp.getSign() + " can't have more than 1 values"); 419 } 420 421 sb.append(comp.getSign()).append(" "); 422 params.putAll(appendParams(sb, filter.getValue(), pcnt)); 423 break; 424 } 425 426 pcnt += filter.getValue().size(); 427 } 428 sb.append(" "); 429 return params; 430 } 431 432 private static Map<String, Object> appendParams(StringBuilder sb, List<Object> value, int sindex) { 433 Map<String, Object> params = new HashMap<String, Object>(); 434 boolean first = true; 435 for (Object val : value) { 436 String pname = "p" + sindex++; 437 params.put(pname, val); 438 if (!first) { 439 sb.append(", "); 440 } 441 sb.append(':').append(pname); 442 first = false; 443 } 444 return params; 445 } 446 447 public static boolean isInputLogicSpecified(String actionXml) throws JDOMException { 448 return isInputLogicSpecified(XmlUtils.parseXml(actionXml)); 449 } 450 451 public static boolean isInputLogicSpecified(Element eAction) throws JDOMException { 452 return eAction.getChild(CoordInputLogicEvaluator.INPUT_LOGIC, eAction.getNamespace()) != null; 453 } 454 455 public static String getInputLogic(String actionXml) throws JDOMException { 456 return getInputLogic(XmlUtils.parseXml(actionXml)); 457 } 458 459 public static String getInputLogic(Element actionXml) throws JDOMException { 460 return new InputLogicParser().parse(actionXml.getChild(CoordInputLogicEvaluator.INPUT_LOGIC, 461 actionXml.getNamespace())); 462 } 463 464}