001 /** 002 * Copyright (c) 2010 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.command.coord; 016 017 import java.io.IOException; 018 import java.io.StringReader; 019 import java.util.Date; 020 import java.util.List; 021 022 import org.apache.hadoop.conf.Configuration; 023 import org.apache.hadoop.fs.Path; 024 import org.apache.oozie.CoordinatorActionBean; 025 import org.apache.oozie.ErrorCode; 026 import org.apache.oozie.client.CoordinatorAction; 027 import org.apache.oozie.client.OozieClient; 028 import org.apache.oozie.command.CommandException; 029 import org.apache.oozie.coord.CoordELEvaluator; 030 import org.apache.oozie.coord.CoordELFunctions; 031 import org.apache.oozie.service.HadoopAccessorException; 032 import org.apache.oozie.service.HadoopAccessorService; 033 import org.apache.oozie.service.Services; 034 import org.apache.oozie.store.CoordinatorStore; 035 import org.apache.oozie.store.StoreException; 036 import org.apache.oozie.util.DateUtils; 037 import org.apache.oozie.util.ELEvaluator; 038 import org.apache.oozie.util.Instrumentation; 039 import org.apache.oozie.util.ParamChecker; 040 import org.apache.oozie.util.XConfiguration; 041 import org.apache.oozie.util.XLog; 042 import org.apache.oozie.util.XmlUtils; 043 import org.jdom.Element; 044 045 public class CoordActionInputCheckCommand extends CoordinatorCommand<Void> { 046 047 private String actionId; 048 private final XLog log = XLog.getLog(getClass()); 049 private int COMMAND_REQUEUE_INTERVAL = 60000; // 1 minute 050 private CoordinatorActionBean coordAction = null; 051 052 public CoordActionInputCheckCommand(String actionId) { 053 super("coord_action_input", "coord_action_input", 1, XLog.STD); 054 this.actionId = actionId; 055 } 056 057 @Override 058 protected Void call(CoordinatorStore store) throws StoreException, CommandException { 059 log.debug("After store.get() for action ID " + actionId + " : " + coordAction.getStatus()); 060 // this action should only get processed if current time > 061 // materialization time 062 // otherwise, requeue this action after 30 seconds 063 Date nominalTime = coordAction.getNominalTime(); 064 Date currentTime = new Date(); 065 if (nominalTime.compareTo(currentTime) > 0) { 066 log.info("[" + actionId 067 + "]::ActionInputCheck:: nominal Time is newer than current time, so requeue and wait. Current=" 068 + currentTime + ", nominal=" + nominalTime); 069 queueCallable(new CoordActionInputCheckCommand(coordAction.getId()), Math.max( 070 (nominalTime.getTime() - currentTime.getTime()), COMMAND_REQUEUE_INTERVAL)); 071 // update lastModifiedTime 072 store.updateCoordinatorAction(coordAction); 073 return null; 074 } 075 if (coordAction.getStatus() == CoordinatorActionBean.Status.WAITING) { 076 log.info("[" + actionId + "]::ActionInputCheck:: Action is in WAITING state."); 077 StringBuilder actionXml = new StringBuilder(coordAction.getActionXml());// job.getXml(); 078 Instrumentation.Cron cron = new Instrumentation.Cron(); 079 try { 080 Configuration actionConf = new XConfiguration(new StringReader(coordAction.getRunConf())); 081 cron.start(); 082 StringBuilder existList = new StringBuilder(); 083 StringBuilder nonExistList = new StringBuilder(); 084 StringBuilder nonResolvedList = new StringBuilder(); 085 CoordCommandUtils.getResolvedList(coordAction.getMissingDependencies(), nonExistList, nonResolvedList); 086 087 String[] uriList = nonExistList.toString().split(CoordELFunctions.INSTANCE_SEPARATOR); 088 if (uriList.length > 0) { 089 log.info("[" + actionId + "]::ActionInputCheck:: Missing deps:" + uriList[0] + ", NonResolvedList:" 090 + nonResolvedList.toString()); 091 } else { 092 log.info("[" + actionId + "]::ActionInputCheck:: No missing deps, NonResolvedList:" 093 + nonResolvedList.toString()); 094 } 095 boolean status = checkInput(actionXml, existList, nonExistList, actionConf); 096 coordAction.setLastModifiedTime(currentTime); 097 coordAction.setActionXml(actionXml.toString()); 098 if (nonResolvedList.length() > 0 && status == false) { 099 nonExistList.append(CoordCommandUtils.RESOLVED_UNRESOLVED_SEPARATOR).append(nonResolvedList); 100 } 101 coordAction.setMissingDependencies(nonExistList.toString()); 102 if (status == true) { 103 coordAction.setStatus(CoordinatorAction.Status.READY); 104 // pass jobID to the ReadyCommand 105 queueCallable(new CoordActionReadyCommand(coordAction.getJobId()), 100); 106 } 107 else { 108 long waitingTime = (currentTime.getTime() - Math.max(coordAction.getNominalTime().getTime(), 109 coordAction.getCreatedTime().getTime())) / (60 * 1000); 110 int timeOut = coordAction.getTimeOut(); 111 if ((timeOut >= 0) && (waitingTime > timeOut)) { 112 queueCallable(new CoordActionTimeOut(coordAction), 100); 113 coordAction.setStatus(CoordinatorAction.Status.TIMEDOUT); 114 } 115 else { 116 queueCallable(new CoordActionInputCheckCommand(coordAction.getId()), COMMAND_REQUEUE_INTERVAL); 117 } 118 } 119 store.updateCoordActionMin(coordAction); 120 } 121 catch (Exception e) { 122 log.warn(actionId + ": Exception occurs: " + e + " STORE is active " + store.isActive(), e); 123 throw new CommandException(ErrorCode.E1005, e.getMessage(), e); 124 } 125 cron.stop(); 126 } 127 else { 128 log.info("[" + actionId + "]::ActionInputCheck:: Ignoring action. Should be in WAITING state, but state=" 129 + coordAction.getStatus()); 130 } 131 return null; 132 } 133 134 protected boolean checkInput(StringBuilder actionXml, StringBuilder existList, StringBuilder nonExistList, 135 Configuration conf) throws Exception { 136 Element eAction = XmlUtils.parseXml(actionXml.toString()); 137 boolean allExist = checkResolvedUris(eAction, existList, nonExistList, conf); 138 if (allExist) { 139 log.debug("[" + actionId + "]::ActionInputCheck:: Checking Latest/future"); 140 allExist = checkUnresolvedInstances(eAction, conf); 141 } 142 if (allExist == true) { 143 materializeDataProperties(eAction, conf); 144 actionXml.replace(0, actionXml.length(), XmlUtils.prettyPrint(eAction).toString()); 145 } 146 return allExist; 147 } 148 149 /** 150 * Materialize data properties defined in <action> tag. it includes dataIn(<DS>) and dataOut(<DS>) it creates a list 151 * of files that will be needed. 152 * 153 * @param eAction 154 * @param conf 155 * @throws Exception 156 * @update modify 'Action' element with appropriate list of files. 157 */ 158 private void materializeDataProperties(Element eAction, Configuration conf) throws Exception { 159 ELEvaluator eval = CoordELEvaluator.createDataEvaluator(eAction, conf, actionId); 160 Element configElem = eAction.getChild("action", eAction.getNamespace()).getChild("workflow", 161 eAction.getNamespace()).getChild("configuration", eAction.getNamespace()); 162 if (configElem != null) { 163 for (Element propElem : (List<Element>) configElem.getChildren("property", configElem.getNamespace())) { 164 resolveTagContents("value", propElem, eval); 165 } 166 } 167 } 168 169 private void resolveTagContents(String tagName, Element elem, ELEvaluator eval) throws Exception { 170 if (elem == null) { 171 return; 172 } 173 Element tagElem = elem.getChild(tagName, elem.getNamespace()); 174 if (tagElem != null) { 175 String updated = CoordELFunctions.evalAndWrap(eval, tagElem.getText()); 176 tagElem.removeContent(); 177 tagElem.addContent(updated); 178 } 179 else { 180 log.warn(" Value NOT FOUND " + tagName); 181 } 182 } 183 184 private boolean checkUnresolvedInstances(Element eAction, Configuration actionConf) 185 throws Exception { 186 String strAction = XmlUtils.prettyPrint(eAction).toString(); 187 Date nominalTime = DateUtils.parseDateUTC(eAction.getAttributeValue("action-nominal-time")); 188 String actualTimeStr = eAction.getAttributeValue("action-actual-time"); 189 Date actualTime = null; 190 if (actualTimeStr == null) { 191 log.debug("Unable to get action-actual-time from action xml, this job is submitted " + 192 "from previous version. Assign current date to actual time, action = " + actionId); 193 actualTime = new Date(); 194 } else { 195 actualTime = DateUtils.parseDateUTC(actualTimeStr); 196 } 197 198 StringBuffer resultedXml = new StringBuffer(); 199 200 boolean ret; 201 Element inputList = eAction.getChild("input-events", eAction.getNamespace()); 202 if (inputList != null) { 203 ret = materializeUnresolvedEvent(inputList.getChildren("data-in", eAction.getNamespace()), nominalTime, 204 actualTime, actionConf); 205 if (ret == false) { 206 resultedXml.append(strAction); 207 return false; 208 } 209 } 210 211 // Using latest() or future() in output-event is not intuitive. 212 // We need to make 213 // sure, this assumption is correct. 214 Element outputList = eAction.getChild("output-events", eAction.getNamespace()); 215 if (outputList != null) { 216 for (Element dEvent : (List<Element>) outputList.getChildren("data-out", eAction.getNamespace())) { 217 if (dEvent.getChild("unresolved-instances", dEvent.getNamespace()) != null) { 218 throw new CommandException(ErrorCode.E1006, "coord:latest()/future()", 219 " not permitted in output-event "); 220 } 221 } 222 /* 223 * ret = materializeUnresolvedEvent( (List<Element>) 224 * outputList.getChildren("data-out", eAction.getNamespace()), 225 * actualTime, nominalTime, actionConf); if (ret == false) { 226 * resultedXml.append(strAction); return false; } 227 */ 228 } 229 return true; 230 } 231 232 private boolean materializeUnresolvedEvent(List<Element> eDataEvents, Date nominalTime, Date actualTime, 233 Configuration conf) throws Exception { 234 for (Element dEvent : eDataEvents) { 235 if (dEvent.getChild("unresolved-instances", dEvent.getNamespace()) == null) { 236 continue; 237 } 238 ELEvaluator eval = CoordELEvaluator.createLazyEvaluator(actualTime, nominalTime, dEvent, conf); 239 String uresolvedInstance = dEvent.getChild("unresolved-instances", dEvent.getNamespace()).getTextTrim(); 240 String unresolvedList[] = uresolvedInstance.split(CoordELFunctions.INSTANCE_SEPARATOR); 241 StringBuffer resolvedTmp = new StringBuffer(); 242 for (int i = 0; i < unresolvedList.length; i++) { 243 String ret = CoordELFunctions.evalAndWrap(eval, unresolvedList[i]); 244 Boolean isResolved = (Boolean) eval.getVariable("is_resolved"); 245 if (isResolved == false) { 246 log.info("[" + actionId + "]::Cannot resolve: " + ret); 247 return false; 248 } 249 if (resolvedTmp.length() > 0) { 250 resolvedTmp.append(CoordELFunctions.INSTANCE_SEPARATOR); 251 } 252 resolvedTmp.append((String) eval.getVariable("resolved_path")); 253 } 254 if (resolvedTmp.length() > 0) { 255 if (dEvent.getChild("uris", dEvent.getNamespace()) != null) { 256 resolvedTmp.append(CoordELFunctions.INSTANCE_SEPARATOR).append( 257 dEvent.getChild("uris", dEvent.getNamespace()).getTextTrim()); 258 dEvent.removeChild("uris", dEvent.getNamespace()); 259 } 260 Element uriInstance = new Element("uris", dEvent.getNamespace()); 261 uriInstance.addContent(resolvedTmp.toString()); 262 dEvent.getContent().add(1, uriInstance); 263 } 264 dEvent.removeChild("unresolved-instances", dEvent.getNamespace()); 265 } 266 267 return true; 268 } 269 270 private boolean checkResolvedUris(Element eAction, StringBuilder existList, StringBuilder nonExistList, 271 Configuration conf) throws IOException { 272 273 log.info("[" + actionId + "]::ActionInputCheck:: In checkResolvedUris..."); 274 Element inputList = eAction.getChild("input-events", eAction.getNamespace()); 275 if (inputList != null) { 276 // List<Element> eDataEvents = inputList.getChildren("data-in", 277 // eAction.getNamespace()); 278 // for (Element event : eDataEvents) { 279 // Element uris = event.getChild("uris", event.getNamespace()); 280 if (nonExistList.length() > 0) { 281 checkListOfPaths(existList, nonExistList, conf); 282 } 283 // } 284 return nonExistList.length() == 0; 285 } 286 return true; 287 } 288 289 private boolean checkListOfPaths(StringBuilder existList, StringBuilder nonExistList, Configuration conf) 290 throws IOException { 291 292 String[] uriList = nonExistList.toString().split(CoordELFunctions.INSTANCE_SEPARATOR); 293 if (uriList[0] != null) { 294 log.info("[" + actionId + "]::ActionInputCheck:: In checkListOfPaths: " + uriList[0] + " is Missing."); 295 } 296 297 nonExistList.delete(0, nonExistList.length()); 298 boolean allExists = true; 299 String existSeparator = "", nonExistSeparator = ""; 300 for (int i = 0; i < uriList.length; i++) { 301 if (allExists) { 302 allExists = pathExists(uriList[i], conf); 303 log.info("[" + actionId + "]::ActionInputCheck:: File:" + uriList[i] + ", Exists? :" + allExists); 304 } 305 if (allExists) { 306 existList.append(existSeparator).append(uriList[i]); 307 existSeparator = CoordELFunctions.INSTANCE_SEPARATOR; 308 } 309 else { 310 nonExistList.append(nonExistSeparator).append(uriList[i]); 311 nonExistSeparator = CoordELFunctions.INSTANCE_SEPARATOR; 312 } 313 } 314 return allExists; 315 } 316 317 private boolean pathExists(String sPath, Configuration actionConf) throws IOException { 318 log.debug("checking for the file " + sPath); 319 Path path = new Path(sPath); 320 String user = ParamChecker.notEmpty(actionConf.get(OozieClient.USER_NAME), OozieClient.USER_NAME); 321 String group = ParamChecker.notEmpty(actionConf.get(OozieClient.GROUP_NAME), OozieClient.GROUP_NAME); 322 try { 323 return Services.get().get(HadoopAccessorService.class).createFileSystem(user, group, path.toUri(), 324 actionConf).exists(path); 325 } 326 catch (HadoopAccessorException e) { 327 throw new IOException(e); 328 } 329 } 330 331 /** 332 * The function create a list of URIs separated by "," using the instances time stamp and URI-template 333 * 334 * @param event : <data-in> event 335 * @param instances : List of time stamp seprated by "," 336 * @param unresolvedInstances : list of instance with latest/future function 337 * @return : list of URIs separated by ",". 338 * @throws Exception 339 */ 340 private String createURIs(Element event, String instances, StringBuilder unresolvedInstances) throws Exception { 341 if (instances == null || instances.length() == 0) { 342 return ""; 343 } 344 String[] instanceList = instances.split(CoordELFunctions.INSTANCE_SEPARATOR); 345 StringBuilder uris = new StringBuilder(); 346 347 for (int i = 0; i < instanceList.length; i++) { 348 int funcType = CoordCommandUtils.getFuncType(instanceList[i]); 349 if (funcType == CoordCommandUtils.LATEST || funcType == CoordCommandUtils.FUTURE) { 350 if (unresolvedInstances.length() > 0) { 351 unresolvedInstances.append(CoordELFunctions.INSTANCE_SEPARATOR); 352 } 353 unresolvedInstances.append(instanceList[i]); 354 continue; 355 } 356 ELEvaluator eval = CoordELEvaluator.createURIELEvaluator(instanceList[i]); 357 // uris.append(eval.evaluate(event.getChild("dataset", 358 // event.getNamespace()).getChild("uri-template", 359 // event.getNamespace()).getTextTrim(), String.class)); 360 if (uris.length() > 0) { 361 uris.append(CoordELFunctions.INSTANCE_SEPARATOR); 362 } 363 uris.append(CoordELFunctions.evalAndWrap(eval, event.getChild("dataset", event.getNamespace()).getChild( 364 "uri-template", event.getNamespace()).getTextTrim())); 365 } 366 return uris.toString(); 367 } 368 369 @Override 370 protected Void execute(CoordinatorStore store) throws StoreException, CommandException { 371 log.info("STARTED CoordActionInputCheckCommand for actionid=" + actionId); 372 try { 373 coordAction = store.getEntityManager().find(CoordinatorActionBean.class, actionId); 374 setLogInfo(coordAction); 375 if (lock(coordAction.getJobId())) { 376 call(store); 377 } 378 else { 379 queueCallable(new CoordActionInputCheckCommand(actionId), LOCK_FAILURE_REQUEUE_INTERVAL); 380 log.warn("CoordActionInputCheckCommand lock was not acquired - failed jobId=" + coordAction.getJobId() 381 + ", actionId=" + actionId + ". Requeing the same."); 382 } 383 } 384 catch (InterruptedException e) { 385 queueCallable(new CoordActionInputCheckCommand(actionId), LOCK_FAILURE_REQUEUE_INTERVAL); 386 log.warn("CoordActionInputCheckCommand lock acquiring failed with exception " + e.getMessage() 387 + " for jobId=" + coordAction.getJobId() + ", actionId=" + actionId + " Requeing the same."); 388 } 389 finally { 390 log.info("ENDED CoordActionInputCheckCommand for actionid=" + actionId); 391 } 392 return null; 393 } 394 395 /* (non-Javadoc) 396 * @see org.apache.oozie.command.Command#getKey() 397 */ 398 @Override 399 public String getKey(){ 400 return getName() + "_" + actionId; 401 } 402 403 }