MyString MultiLogFiles::loadValueFromSubFile(const MyString &strSubFilename, const MyString &directory, const char *keyword) { dprintf( D_FULLDEBUG, "MultiLogFiles::loadValueFromSubFile(%s, %s, %s)\n", strSubFilename.Value(), directory.Value(), keyword ); TmpDir td; if ( directory != "" ) { MyString errMsg; if ( !td.Cd2TmpDir(directory.Value(), errMsg) ) { dprintf(D_ALWAYS, "Error from Cd2TmpDir: %s\n", errMsg.Value()); return ""; } } StringList logicalLines; if ( fileNameToLogicalLines( strSubFilename, logicalLines ) != "" ) { return ""; } MyString value(""); // Now look through the submit file logical lines to find the // value corresponding to the keyword. const char *logicalLine; while( (logicalLine = logicalLines.next()) != NULL ) { MyString submitLine(logicalLine); MyString tmpValue = getParamFromSubmitLine(submitLine, keyword); if ( tmpValue != "" ) { value = tmpValue; } } // // Check for macros in the value -- we currently don't // handle those. // if ( value != "" ) { if ( strchr(value.Value(), '$') ) { dprintf(D_ALWAYS, "MultiLogFiles: macros not allowed " "in %s in DAG node submit files\n", keyword); value = ""; } } if ( directory != "" ) { MyString errMsg; if ( !td.Cd2MainDir(errMsg) ) { dprintf(D_ALWAYS, "Error from Cd2MainDir: %s\n", errMsg.Value()); return ""; } } return value; }
//----------------------------------------------------------------------------- int Script::BackgroundRun( int reaperId, int dagStatus, int failedCount ) { TmpDir tmpDir; MyString errMsg; if ( !tmpDir.Cd2TmpDir( _node->GetDirectory(), errMsg ) ) { debug_printf( DEBUG_QUIET, "Could not change to node directory %s: %s\n", _node->GetDirectory(), errMsg.Value() ); return 0; } // Construct the command line, replacing some tokens with // information about the job. All of these values would probably // be better inserted into the environment, rather than passed on // the command-line... some should be in the job's env as well... const char *delimiters = " \t"; char * token; ArgList args; char * cmd = strnewp(_cmd); for (token = strtok (cmd, delimiters) ; token != NULL ; token = strtok (NULL, delimiters)) { MyString arg; if ( !strcasecmp( token, "$JOB" ) ) { arg += _node->GetJobName(); } else if ( !strcasecmp( token, "$RETRY" ) ) { arg += _node->GetRetries(); } else if ( !strcasecmp( token, "$MAX_RETRIES" ) ) { arg += _node->GetRetryMax(); } else if ( !strcasecmp( token, "$JOBID" ) ) { if ( !_post ) { debug_printf( DEBUG_QUIET, "Warning: $JOBID macro should " "not be used as a PRE script argument!\n" ); check_warning_strictness( DAG_STRICT_1 ); arg += token; } else { arg += _node->_CondorID._cluster; arg += '.'; arg += _node->_CondorID._proc; } } else if (!strcasecmp(token, "$RETURN")) { if ( !_post ) { debug_printf( DEBUG_QUIET, "Warning: $RETURN macro should " "not be used as a PRE script argument!\n" ); check_warning_strictness( DAG_STRICT_1 ); } arg += _retValJob; } else if (!strcasecmp( token, "$PRE_SCRIPT_RETURN" ) ) { if ( !_post ) { debug_printf( DEBUG_QUIET, "Warning: $PRE_SCRIPT_RETURN macro should " "not be used as a PRE script argument!\n" ); check_warning_strictness( DAG_STRICT_1 ); } arg += _retValScript; } else if (!strcasecmp(token, "$DAG_STATUS")) { arg += dagStatus; } else if (!strcasecmp(token, "$FAILED_COUNT")) { arg += failedCount; } else if (token[0] == '$') { // This should probably be a fatal error when -strict is // implemented. debug_printf( DEBUG_QUIET, "Warning: unrecognized macro %s " "in node %s %s script arguments\n", token, _node->GetJobName(), _post ? "POST" : "PRE" ); check_warning_strictness( DAG_STRICT_1 ); arg += token; } else { arg += token; } args.AppendArg(arg.Value()); } _pid = daemonCore->Create_Process( cmd, args, PRIV_UNKNOWN, reaperId, FALSE, NULL, NULL, NULL, NULL, NULL, 0 ); delete [] cmd; if ( !tmpDir.Cd2MainDir( errMsg ) ) { debug_printf( DEBUG_QUIET, "Could not change to original directory: %s\n", errMsg.Value() ); return 0; } return _pid; }
//------------------------------------------------------------------------- bool condor_submit( const Dagman &dm, const char* cmdFile, CondorID& condorID, const char* DAGNodeName, MyString &DAGParentNodeNames, List<Job::NodeVar> *vars, int retry, const char* directory, const char *workflowLogFile, bool hold_claim ) { TmpDir tmpDir; MyString errMsg; if ( !tmpDir.Cd2TmpDir( directory, errMsg ) ) { debug_printf( DEBUG_QUIET, "Could not change to node directory %s: %s\n", directory, errMsg.Value() ); return false; } ArgList args; // construct arguments to condor_submit to add attributes to the // job classad which identify the job's node name in the DAG, the // node names of its parents in the DAG, and the job ID of DAGMan // itself; then, define submit_event_notes to print the job's node // name inside the submit event in the userlog // NOTE: we specify the job ID of DAGMan using only its cluster ID // so that it may be referenced by jobs in their priority // attribute (which needs an int, not a string). Doing so allows // users to effectively "batch" jobs by DAG so that when they // submit many DAGs to the same schedd, all the ready jobs from // one DAG complete before any jobs from another begin. args.AppendArg( dm.condorSubmitExe ); args.AppendArg( "-a" ); MyString nodeName = MyString(ATTR_DAG_NODE_NAME_ALT) + " = " + DAGNodeName; args.AppendArg( nodeName.Value() ); // append a line adding the parent DAGMan's cluster ID to the job ad args.AppendArg( "-a" ); MyString dagJobId = MyString( "+" ) + ATTR_DAGMAN_JOB_ID + " = " + dm.DAGManJobId._cluster; args.AppendArg( dagJobId.Value() ); // now we append a line setting the same thing as a submit-file macro // (this is necessary so the user can reference it in the priority) args.AppendArg( "-a" ); MyString dagJobIdMacro = MyString( "" ) + ATTR_DAGMAN_JOB_ID + " = " + dm.DAGManJobId._cluster; args.AppendArg( dagJobIdMacro.Value() ); args.AppendArg( "-a" ); MyString submitEventNotes = MyString( "submit_event_notes = DAG Node: " ) + DAGNodeName; args.AppendArg( submitEventNotes.Value() ); ASSERT( workflowLogFile ); // We need to append the DAGman default log file to // the log file list args.AppendArg( "-a" ); std::string dlog( "dagman_log = " ); dlog += workflowLogFile; args.AppendArg( dlog.c_str() ); debug_printf( DEBUG_VERBOSE, "Adding a DAGMan workflow log %s\n", workflowLogFile ); // Now append the mask debug_printf( DEBUG_VERBOSE, "Masking the events recorded in the DAGMAN workflow log\n" ); args.AppendArg( "-a" ); std::string dmask("+"); dmask += ATTR_DAGMAN_WORKFLOW_MASK; dmask += " = \""; const char *eventMask = getEventMask(); debug_printf( DEBUG_VERBOSE, "Mask for workflow log is %s\n", eventMask ); dmask += eventMask; dmask += "\""; args.AppendArg( dmask.c_str() ); // Suppress the job's log file if that option is enabled. if ( dm._suppressJobLogs ) { debug_printf( DEBUG_VERBOSE, "Suppressing node job log file\n" ); args.AppendArg( "-a" ); args.AppendArg( "log = ''" ); } ArgList parentNameArgs; parentNameArgs.AppendArg( "-a" ); MyString parentNodeNames = MyString( "+DAGParentNodeNames = " ) + "\"" + DAGParentNodeNames + "\""; parentNameArgs.AppendArg( parentNodeNames.Value() ); // set any VARS specified in the DAG file MyString anotherLine; ListIterator<Job::NodeVar> varsIter(*vars); Job::NodeVar nodeVar; while ( varsIter.Next(nodeVar) ) { // Substitute the node retry count if necessary. Note that // we can't do this in Job::ResolveVarsInterpolations() // because that's only called at parse time. MyString value = nodeVar._value; MyString retryStr( retry ); value.replaceString( "$(RETRY)", retryStr.Value() ); MyString varStr = nodeVar._name + " = " + value; args.AppendArg( "-a" ); args.AppendArg( varStr.Value() ); } // Set the special DAG_STATUS variable (mainly for use by // "final" nodes). args.AppendArg( "-a" ); MyString var = "DAG_STATUS = "; var += dm.dag->_dagStatus; args.AppendArg( var.Value() ); // Set the special FAILED_COUNT variable (mainly for use by // "final" nodes). args.AppendArg( "-a" ); var = "FAILED_COUNT = "; var += dm.dag->NumNodesFailed(); args.AppendArg( var.Value() ); // how big is the command line so far MyString display; args.GetArgsStringForDisplay( &display ); int cmdLineSize = display.Length(); parentNameArgs.GetArgsStringForDisplay( &display ); int DAGParentNodeNamesLen = display.Length(); // how many additional chars must we still add to command line // NOTE: according to the POSIX spec, the args + // environ given to exec() cannot exceed // _POSIX_ARG_MAX, so we also need to calculate & add // the size of environ** to reserveNeeded int reserveNeeded = strlen( cmdFile ); int maxCmdLine = _POSIX_ARG_MAX; // if we don't have room for DAGParentNodeNames, leave it unset if( cmdLineSize + reserveNeeded + DAGParentNodeNamesLen > maxCmdLine ) { debug_printf( DEBUG_NORMAL, "Warning: node %s has too many parents " "to list in its classad; leaving its DAGParentNodeNames " "attribute undefined\n", DAGNodeName ); check_warning_strictness( DAG_STRICT_3 ); } else { args.AppendArgsFromArgList( parentNameArgs ); } if( hold_claim ){ args.AppendArg( "-a" ); MyString holdit = MyString("+") + MyString(ATTR_JOB_KEEP_CLAIM_IDLE) + " = " + dm._claim_hold_time; args.AppendArg( holdit.Value() ); } if (dm._submitDagDeepOpts.suppress_notification) { args.AppendArg( "-a" ); MyString notify = MyString("notification = never"); args.AppendArg( notify.Value() ); } args.AppendArg( cmdFile ); bool success = do_submit( args, condorID, dm.prohibitMultiJobs ); if ( !tmpDir.Cd2MainDir( errMsg ) ) { debug_printf( DEBUG_QUIET, "Could not change to original directory: %s\n", errMsg.Value() ); success = false; } return success; }
//------------------------------------------------------------------------- bool GetConfigAndAttrs( /* const */ StringList &dagFiles, bool useDagDir, MyString &configFile, StringList &attrLines, MyString &errMsg ) { bool result = true; // Note: destructor will change back to original directory. TmpDir dagDir; dagFiles.rewind(); char *dagFile; while ( (dagFile = dagFiles.next()) != NULL ) { // // Change to the DAG file's directory if necessary, and // get the filename we need to use for it from that directory. // const char * newDagFile; if ( useDagDir ) { MyString tmpErrMsg; if ( !dagDir.Cd2TmpDirFile( dagFile, tmpErrMsg ) ) { AppendError( errMsg, MyString("Unable to change to DAG directory ") + tmpErrMsg ); return false; } newDagFile = condor_basename( dagFile ); } else { newDagFile = dagFile; } StringList configFiles; // Note: destructor will close file. MultiLogFiles::FileReader reader; errMsg = reader.Open( newDagFile ); if ( errMsg != "" ) { return false; } MyString logicalLine; while ( reader.NextLogicalLine( logicalLine ) ) { if ( logicalLine != "" ) { // Note: StringList constructor removes leading // whitespace from lines. StringList tokens( logicalLine.Value(), " \t" ); tokens.rewind(); const char *firstToken = tokens.next(); if ( !strcasecmp( firstToken, "config" ) ) { // Get the value. const char *newValue = tokens.next(); if ( !newValue || !strcmp( newValue, "" ) ) { AppendError( errMsg, "Improperly-formatted " "file: value missing after keyword " "CONFIG" ); result = false; } else { // Add the value we just found to the config // files list (if it's not already in the // list -- we don't want duplicates). configFiles.rewind(); char *existingValue; bool alreadyInList = false; while ( ( existingValue = configFiles.next() ) ) { if ( !strcmp( existingValue, newValue ) ) { alreadyInList = true; } } if ( !alreadyInList ) { // Note: append copies the string here. configFiles.append( newValue ); } } //some DAG commands are needed for condor_submit_dag, too... } else if ( !strcasecmp( firstToken, "SET_JOB_ATTR" ) ) { // Strip of DAGMan-specific command name; the // rest we pass to the submit file. logicalLine.replaceString( "SET_JOB_ATTR", "" ); logicalLine.trim(); if ( logicalLine == "" ) { AppendError( errMsg, "Improperly-formatted " "file: value missing after keyword " "SET_JOB_ATTR" ); result = false; } else { attrLines.append( logicalLine.Value() ); } } } } reader.Close(); // // Check the specified config file(s) against whatever we // currently have, setting the config file if it hasn't // been set yet, flagging an error if config files conflict. // configFiles.rewind(); char * cfgFile; while ( (cfgFile = configFiles.next()) ) { MyString cfgFileMS = cfgFile; MyString tmpErrMsg; if ( MakePathAbsolute( cfgFileMS, tmpErrMsg ) ) { if ( configFile == "" ) { configFile = cfgFileMS; } else if ( configFile != cfgFileMS ) { AppendError( errMsg, MyString("Conflicting DAGMan ") + "config files specified: " + configFile + " and " + cfgFileMS ); result = false; } } else { AppendError( errMsg, tmpErrMsg ); result = false; } } // // Go back to our original directory. // MyString tmpErrMsg; if ( !dagDir.Cd2MainDir( tmpErrMsg ) ) { AppendError( errMsg, MyString("Unable to change to original directory ") + tmpErrMsg ); result = false; } } return result; }
//--------------------------------------------------------------------------- int runSubmitDag( const SubmitDagDeepOptions &deepOpts, const char *dagFile, const char *directory, int priority, bool isRetry ) { int result = 0; // Change to the appropriate directory if necessary. TmpDir tmpDir; MyString errMsg; if ( directory ) { if ( !tmpDir.Cd2TmpDir( directory, errMsg ) ) { fprintf( stderr, "Error (%s) changing to node directory\n", errMsg.Value() ); result = 1; return result; } } // Build up the command line for the recursive run of // condor_submit_dag. We need -no_submit so we don't // actually run the subdag now; we need -update_submit // so the lower-level .condor.sub file will get // updated, in case it came from an earlier version // of condor_submit_dag. ArgList args; args.AppendArg( "condor_submit_dag" ); args.AppendArg( "-no_submit" ); args.AppendArg( "-update_submit" ); // Add in arguments we're passing along. if ( deepOpts.bVerbose ) { args.AppendArg( "-verbose" ); } if ( deepOpts.bForce && !isRetry ) { args.AppendArg( "-force" ); } if (deepOpts.strNotification != "" ) { args.AppendArg( "-notification" ); if(deepOpts.suppress_notification) { args.AppendArg( "never" ); } else { args.AppendArg( deepOpts.strNotification.Value() ); } } if ( deepOpts.strDagmanPath != "" ) { args.AppendArg( "-dagman" ); args.AppendArg( deepOpts.strDagmanPath.Value() ); } if ( deepOpts.useDagDir ) { args.AppendArg( "-usedagdir" ); } if ( deepOpts.strOutfileDir != "" ) { args.AppendArg( "-outfile_dir" ); args.AppendArg( deepOpts.strOutfileDir.Value() ); } args.AppendArg( "-autorescue" ); args.AppendArg( deepOpts.autoRescue ); if ( deepOpts.doRescueFrom != 0 ) { args.AppendArg( "-dorescuefrom" ); args.AppendArg( deepOpts.doRescueFrom ); } if ( deepOpts.allowVerMismatch ) { args.AppendArg( "-allowver" ); } if ( deepOpts.importEnv ) { args.AppendArg( "-import_env" ); } if ( deepOpts.recurse ) { args.AppendArg( "-do_recurse" ); } if ( deepOpts.updateSubmit ) { args.AppendArg( "-update_submit" ); } if( priority != 0) { args.AppendArg( "-Priority" ); args.AppendArg( priority ); } if( deepOpts.suppress_notification ) { args.AppendArg( "-suppress_notification" ); } else { args.AppendArg( "-dont_suppress_notification" ); } args.AppendArg( dagFile ); MyString cmdLine; args.GetArgsStringForDisplay( &cmdLine ); dprintf( D_ALWAYS, "Recursive submit command: <%s>\n", cmdLine.Value() ); // Now actually run the command. int retval = my_system( args ); if ( retval != 0 ) { dprintf( D_ALWAYS, "ERROR: condor_submit_dag -no_submit " "failed on DAG file %s.\n", dagFile ); result = 1; } // Change back to the directory we started from. if ( !tmpDir.Cd2MainDir( errMsg ) ) { dprintf( D_ALWAYS, "Error (%s) changing back to original directory\n", errMsg.Value() ); } return result; }
/////////////////////////////////////////////////////////////////////////////// // Note: this method should get speeded up (see Gnats PR 846). MyString MultiLogFiles::loadLogFileNameFromSubFile(const MyString &strSubFilename, const MyString &directory, bool &isXml, bool usingDefaultNode) { dprintf( D_FULLDEBUG, "MultiLogFiles::loadLogFileNameFromSubFile(%s, %s)\n", strSubFilename.Value(), directory.Value() ); TmpDir td; if ( directory != "" ) { MyString errMsg; if ( !td.Cd2TmpDir(directory.Value(), errMsg) ) { dprintf(D_ALWAYS, "Error from Cd2TmpDir: %s\n", errMsg.Value()); return ""; } } StringList logicalLines; if ( fileNameToLogicalLines( strSubFilename, logicalLines ) != "" ) { return ""; } MyString logFileName(""); MyString initialDir(""); MyString isXmlLogStr(""); // Now look through the submit file logical lines to find the // log file and initial directory (if specified) and combine // them into a path to the log file that's either absolute or // relative to the DAG submit directory. Also look for log_xml. const char *logicalLine; while( (logicalLine = logicalLines.next()) != NULL ) { MyString submitLine(logicalLine); MyString tmpLogName = getParamFromSubmitLine(submitLine, "log"); if ( tmpLogName != "" ) { logFileName = tmpLogName; } // If we are using the default node log, we don't care // about these if( !usingDefaultNode ) { MyString tmpInitialDir = getParamFromSubmitLine(submitLine, "initialdir"); if ( tmpInitialDir != "" ) { initialDir = tmpInitialDir; } MyString tmpLogXml = getParamFromSubmitLine(submitLine, "log_xml"); if ( tmpLogXml != "" ) { isXmlLogStr = tmpLogXml; } } } if ( !usingDefaultNode ) { // // Check for macros in the log file name -- we currently don't // handle those. // // If we are using the default node, we don't need to check this if ( logFileName != "" ) { if ( strstr(logFileName.Value(), "$(") ) { dprintf(D_ALWAYS, "MultiLogFiles: macros ('$(...') not allowed " "in log file name (%s) in DAG node submit files\n", logFileName.Value()); logFileName = ""; } } // Do not need to prepend initialdir if we are using the // default node log if ( logFileName != "" ) { // Prepend initialdir to log file name if log file name is not // an absolute path. if ( initialDir != "" && !fullpath(logFileName.Value()) ) { logFileName = initialDir + DIR_DELIM_STRING + logFileName; } // We do this in case the same log file is specified with a // relative and an absolute path. // Note: we now do further checking that doesn't rely on // comparing paths to the log files. wenger 2004-05-27. CondorError errstack; if ( !makePathAbsolute( logFileName, errstack ) ) { dprintf(D_ALWAYS, "%s\n", errstack.getFullText().c_str()); return ""; } } isXmlLogStr.lower_case(); isXml = (isXmlLogStr == "true"); if ( directory != "" ) { MyString errMsg; if ( !td.Cd2MainDir(errMsg) ) { dprintf(D_ALWAYS, "Error from Cd2MainDir: %s\n", errMsg.Value()); return ""; } } } return logFileName; }