/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#ifndef CONDOR_HOLDCODES_H
#define CONDOR_HOLDCODES_H


/* This file contains hold reason codes.

   They are stored in a reflective enum class CONDOR_HOLD_CODE via enum.h 
   (see http://aantron.github.io/better-enums/ for complete docs on Better Enums).
   The TLDR is you can use these enums as you would expect, including conversions to ints, like so:
       int code = CONDOR_HOLD_CODE::JobPolicy;
       switch (code) {
	        CONDOR_HOLD_CODE::GlobalGramError:
            ...
       }
   But you can also do a bunch more than with normal enums.  For example, you can fetch 
   the name of the enum as a string with method _to_string, and create an enum from an 
   int with _from_integral.  A pithy example using both:
       int reason_code = 1;   // code 1 is CONDOR_HOLD_CODE::UserRequest
       const char *holdstr = (CONDOR_HOLD_CODE::_from_integral(reason_code))._to_string();
       // Now holdstr = "UserRequst"
   There is a lot more, see the docs for BetterEnums at the url above.
 */

#define BETTER_ENUMS_MACRO_FILE "enum_larger.h"
#include "enum.h" 
BETTER_ENUM(CONDOR_HOLD_CODE, int,

	//There may still be some lingering cases that result in this
	//unspecified hold code.  Hopefully they will be eliminated soon.
	Unspecified = 0,

	//User put the job on hold with condor_hold
	UserRequest = 1,

	//Globus reported an error.  The subcode is the GRAM error number.
	GlobusGramError = 2,

	//The periodic hold expression evaluated to true
	JobPolicy = 3,

	//The credentials for the job (e.g. X509 proxy file) are invalid.
	CorruptedCredential = 4,

	//A job policy expression (such as PeriodicHold) evaluated to UNDEFINED.
	JobPolicyUndefined = 5,

	//The condor_starter failed to start the executable.
	//The subcode will contain the unix errno.
	FailedToCreateProcess = 6,

	//The standard output file for the job could not be opened.
	//The subcode will contain the unix errno.
	UnableToOpenOutput = 7,

	//The standard input file for the job could not be opened.
	//The subcode will contain the unix errno.
	UnableToOpenInput = 8,

	//The standard output stream for the job could not be opened.
	//The subcode will contain the unix errno.
	UnableToOpenOutputStream = 9,

	//The standard input stream for the job could not be opened.
	//The subcode will contain the unix errno.
	UnableToOpenInputStream = 10,

	//An internal Condor protocol error was encountered when transferring files.
	InvalidTransferAck = 11,

	//There was a failure transferring the job's output (or checkpoint) files
	//from the EP back to the the AP.
	//The subcode will contain the unix errno.
	TransferOutputError = 12,

	//There was a failure transferring the job's input files
	//from the AP to the EP.
	//The subcode will contain the unix errno.
	TransferInputError = 13,

	//The initial working directory of the job cannot be accessed.
	//The subcode will contain the unix errno.
	IwdError = 14,

	//The user requested the job be submitted on hold.
	SubmittedOnHold = 15,

	//Input files are being spooled.
	SpoolingInput = 16,

	//In the standard universe, the job and shadows versions aren't
	//compatible.
	JobShadowMismatch = 17,

	//An internal Condor protocol error was encountered when transferring files.
	InvalidTransferGoAhead = 18,

	/**
	   HOOK_PREPARE_JOB was defined but couldn't execute or returned failure.
	   The hold subcode will be 0 if we failed to execute, the exit status
	   if it exited with a failure code, or a negative number with the signal
	   number if it was killed by a signal (e.g. -9).
	*/
	HookPrepareJobFailure = 19,

	MissedDeferredExecutionTime = 20,

	StartdHeldJob = 21,

	// There was a problem opening or otherwise initializing
	// the user log for writing.
	UnableToInitUserLog = 22,

	FailedToAccessUserAccount = 23,

	NoCompatibleShadow = 24,

	InvalidCronSettings = 25,

	// The SYSTEM_PERIODIC_HOLD expression put the job on hold
	SystemPolicy = 26,

	SystemPolicyUndefined = 27,

	GlexecChownSandboxToUser = 28,

	PrivsepChownSandboxToUser = 29,

	GlexecChownSandboxToCondor = 30,

	PrivsepChownSandboxToCondor = 31,

	MaxTransferInputSizeExceeded = 32,

	MaxTransferOutputSizeExceeded = 33,

	JobOutOfResources = 34,

	InvalidDockerImage = 35,

	FailedToCheckpoint = 36,

	EC2UserError = 37,
	EC2InternalError = 38,
	EC2AdminError = 39,
	EC2ConnectionProblem = 40,
	EC2ServerError = 41,
	EC2InstancePotentiallyLostError = 42,

	PreScriptFailed = 43,
	PostScriptFailed = 44,

	// Running singularity test before a sinularity job returned non-zero
	SingularityTestFailed = 45,

	JobDurationExceeded = 46,
	JobExecuteExceeded = 47,

	HookShadowPrepareJobFailure = 48,

	CannotSwitchPrimaryGroup = 49,

	VacateBase = 1000,
	JobPolicyVacate = 1000,
	SystemPolicyVacate = 1001,
	ShadowException = 1002,
	JobNotStarted = 1003,
	UserVacateJob = 1004,
	JobShouldRequeue = 1005,
	FailedToActivateClaim = 1006,  // (obsolete?) generic reason shadow could not activate the claim (see ActivationRefused* codes)
	StarterError = 1007,
	ReconnectFailed = 1008,
	ClaimDeactivated = 1009,
	StartdVacateCommand = 1010,
	StartdPreemptExpression = 1011,
	StartdException = 1012,
	StartdShutdown = 1013,
	StartdDraining = 1014,
	StartdCoalesce = 1015,
	StartdHibernate = 1016,
	StartdReleaseCommand = 1017,
	StartdPreemptingClaimRank = 1018,
	StartdPreemptingClaimUserPrio = 1019,
	VMError = 1020,
	ContainerError = 1021,
	ScheddVacate = 1022,
	JobRemoved = 1023,
	ScratchDirError = 1024,
	SuccessfulCheckpoint = 1025
	,ActivationRefusedBadRequest = 1026      // activation request ad (job) has missing or invalid attributes
	,ActivationRefusedNoMatch = 1027         // slot does not match (see Analyze attribute of reply)
	,ActivationRefusedStillCleaning = 1028   // slot is still cleaning up from previous activation
	,ActivationRefusedWorklifeExpired = 1029 // claim worklife expired
	,ActivationRefusedPreempted = 1030       // claim was preempted
	,ActivationRefusedBroken  = 1031         // slot is broken
	,ActivationRefusedNotIdle = 1032         // slot is not idle and there is no better reason code.
	,ActivationRefusedUnclaimed = 1033       // slot is not claimed
	,ActivationRefusedClaimNotFound = 1034   // claim id was not found
	,ActivationRefusedOldClaim = 1035        // claim id existed once, but has been consumed (future)
	,ActivationRefusedUnhealthy = 1036       // slot is unhealthy (future)
	,StartdRehoming = 1037                   // startd is being rehomed to a different schedd
	// NOTE!!! If you add a new hold code here, don't forget to update the Appendix in the Manual for Job ClassAds!
)

BETTER_ENUM(CONDOR_HOLD_SUBCODE, int,

	// These are enums meant to be used as subcodes for the hold codes above.
	// While each hold code could assign a different meaning to a given subcode, 
	// there is value to having some standard meanings for subcodes across multiple hold codes.
	// Recall any subcode less than or equal to -1000 means vacate (by default) instead of hold.

	Unspecified = 0
	,FileTransferPluginNotFound = -1001
	,FileTransferPluginNotOperational = -1002
	,FileTransferPluginExecFailed = -1003
	,FileTransferPluginNoResultReported = -1004

)

BETTER_ENUM(FILETRANSFER_HOLD_CODE, int,

	// These are two enums are only to be used by the FileTransfer object.

	//The starter or shadow failed to receive or write job files.
	//The subcode will contain the unix errno.
	DownloadFileError = 12,

	//The starter or shadow failed to read or send job files.
	//The subcode will contain the unix errno.
	UploadFileError = 13

)

// Job Out Of Resources (OOR) SubHoldCode
BETTER_ENUM(OUT_OF_RESOURCES_SUB_CODE, int,
	Memory = 102,
	Disk = 104
)

// Why didn't the job start?  Except for CatalogNameError, all of these are
// vacate codes indicating that the job should go into cooldown (to avoid
// immediately re-running it on the same slot).  CatalogNameError is a hold
// code: it's unlikely to be the submitter's fault, but trying again with a
// new shadow or starter likely won't fix the problem.
BETTER_ENUM(JOB_NOT_STARTED_SUB_CODE, int,
	CommonTransferBadReply = 1,
	CommonTransferFailed = 2,
	CommonMappingFailed = 3,
	SlotColoringFailed = 4,
	SlotColoringBadReply = 5,
	OfferResourcesFailed = 6,

	CatalogNameError = 7
)

// Hold reason subcode for final transfer on remove
// Used when condor_rm -transfer triggers a vacate with file transfer
#define HOLD_SUBCODE_FINAL_TRANSFER_ON_REMOVE 1

/* Helper functions to decipher hold codes and subcodes.

   These functions help determine if a job should be held or vacated based on its hold codes and subcodes.
   Any code greater than or equal to 1000 means vacate instead of hold. An unspecified error is also vacate.
   Also any subcode less than or equal to -1000 also means vacate instead of hold.
*/
inline bool shouldVacateJobBasedOnCodes(int code, int subcode) {
	const int codeVacateBase = CONDOR_HOLD_CODE::VacateBase;
	const int subcodeVacateBase = -1 * CONDOR_HOLD_CODE::VacateBase;
	if (code <= 0) {
		// unspecified error; vacate instead of hold.
		return true;
	}
	if (code >= codeVacateBase) {
		return true;
	}
	if (subcode <= subcodeVacateBase) {
		return true;
	}
	return false;
}

inline bool shouldHoldJobBasedOnCodes(int code, int subcode) {
	return !shouldVacateJobBasedOnCodes(code, subcode);
}

// Any (code, subcode) tuple which is not known to a cool-down code isn't.
inline bool shouldCoolJobBasedOnCodes(int vacateCode, int vacateSubCode) {
	if( vacateCode == CONDOR_HOLD_CODE::JobNotStarted ) {
		auto jnssc = JOB_NOT_STARTED_SUB_CODE::_from_integral_nothrow(vacateSubCode);
		if(! jnssc) { return false; }

		switch( * jnssc ) {
			case JOB_NOT_STARTED_SUB_CODE::CommonTransferBadReply:
			case JOB_NOT_STARTED_SUB_CODE::CommonTransferFailed:
			case JOB_NOT_STARTED_SUB_CODE::CommonMappingFailed:
			case JOB_NOT_STARTED_SUB_CODE::SlotColoringFailed:
			case JOB_NOT_STARTED_SUB_CODE::SlotColoringBadReply:
			case JOB_NOT_STARTED_SUB_CODE::OfferResourcesFailed:
				return true;

			case JOB_NOT_STARTED_SUB_CODE::CatalogNameError:
				return false;

			// Don't add `default` here, so that if we add a new value to
			// JOB_NOT_STARTED_SUB_CODE, we have to explicitly decide if
			// it should default to cooling down or not.
		}
	}

	return false;
}

#endif
