397 lines
14 KiB
C++
397 lines
14 KiB
C++
/*
|
|
Rex - A configuration management and workflow automation tool that
|
|
compiles and runs in minimal environments.
|
|
|
|
© SILO GROUP and Chris Punches, 2020.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "Task.h"
|
|
|
|
|
|
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
|
class Task_InvalidDataStructure: public std::runtime_error {
|
|
public:
|
|
Task_InvalidDataStructure(): std::runtime_error("Task: Attempted to access a member of a Task that is not set.") {}
|
|
};
|
|
|
|
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
|
class Task_NotReady: public std::runtime_error {
|
|
public:
|
|
Task_NotReady(): std::runtime_error("Task: Attempted to execute a Task whose Unit is not well defined.") {}
|
|
};
|
|
|
|
|
|
/// Task_RequiredButFailedTask - Exception thrown when a Task fails but should not.
|
|
class TaskException: public std::exception
|
|
{
|
|
public:
|
|
/** Constructor (C strings).
|
|
* @param message C-style string error message.
|
|
* The string contents are copied upon construction.
|
|
* Hence, responsibility for deleting the char* lies
|
|
* with the caller.
|
|
*/
|
|
explicit TaskException(const char* message):
|
|
msg_(message)
|
|
{
|
|
}
|
|
|
|
/** Constructor (C++ STL strings).
|
|
* @param message The error message.
|
|
*/
|
|
explicit TaskException(const std::string& message):
|
|
msg_(message)
|
|
{}
|
|
|
|
/** Destructor.
|
|
* Virtual to allow for subclassing.
|
|
*/
|
|
virtual ~TaskException() throw (){}
|
|
|
|
/** Returns a pointer to the (constant) error description.
|
|
* @return A pointer to a const char*. The underlying memory
|
|
* is in posession of the Exception object. Callers must
|
|
* not attempt to free the memory.
|
|
*/
|
|
virtual const char* what() const throw (){
|
|
return msg_.c_str();
|
|
}
|
|
|
|
protected:
|
|
/** Error message.
|
|
*/
|
|
std::string msg_;
|
|
};
|
|
|
|
|
|
/// Task::Task() - Constructor for the Task class. The Task is the building block of a Plan indicating of which Unit to
|
|
/// execute, and its dependencies on other units to have already been completed successfully.
|
|
Task::Task( int LOG_LEVEL ):
|
|
slog( LOG_LEVEL, "_task_" ),
|
|
definition( LOG_LEVEL )
|
|
{
|
|
// it hasn't executed yet.
|
|
this->complete = false;
|
|
|
|
// it hasn't been matched with a definition yet.
|
|
this->defined = false;
|
|
|
|
this->LOG_LEVEL = LOG_LEVEL;
|
|
}
|
|
|
|
/// Task::load_root() - loads json values to private members
|
|
///
|
|
/// \param loader_root - the Json::Value to populate from.
|
|
/// \param verbose - Whether to print verbose information to STDOUT.
|
|
void Task::load_root(Json::Value loader_root )
|
|
{
|
|
if ( loader_root.isMember("name") ) {
|
|
this->name = loader_root.get("name", "?").asString();
|
|
}
|
|
else {
|
|
throw Task_InvalidDataStructure();
|
|
}
|
|
|
|
// fetch as Json::Value array obj
|
|
Json::Value des_dep_root = loader_root.get("dependencies", 0);
|
|
|
|
// iterate through each member of that obj
|
|
for ( int i = 0; i < des_dep_root.size(); i++ ) {
|
|
// add each string to dependencies
|
|
if ( des_dep_root[i].asString() != "" )
|
|
{
|
|
this->dependencies.push_back( des_dep_root[i].asString() );
|
|
this->slog.log( E_INFO, "Added dependency \"" + des_dep_root[i].asString() + "\" to task \"" + this->get_name() + "\"." );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Task::get_name - Retrieves the name of the current Task.
|
|
std::string Task::get_name()
|
|
{
|
|
return this->name;
|
|
}
|
|
|
|
/// Task::load_definition - Loads a unit to a local member. Used to tie Units to Tasks.
|
|
///
|
|
/// \param selected_unit - The unit to attach.
|
|
/// \param verbose - Whether to print to STDOUT.
|
|
void Task::load_definition( Unit selected_unit )
|
|
{
|
|
this->definition = selected_unit;
|
|
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" as task in configured plan.");
|
|
this->defined = true;
|
|
}
|
|
|
|
/// Task::is_complete - Indicator if the task executed successfully.
|
|
bool Task::is_complete()
|
|
{
|
|
return this->complete;
|
|
}
|
|
|
|
|
|
/// Task::mark_complete - Marks the task complete..
|
|
void Task::mark_complete()
|
|
{
|
|
this->complete = true;
|
|
}
|
|
|
|
|
|
/// Task::get_dependencies - returns a pointer to the dependencies vector.
|
|
std::vector<std::string> Task::get_dependencies()
|
|
{
|
|
return this->dependencies;
|
|
}
|
|
|
|
/// Task::has_definition - Indicator if the task has attached its definition from a Suite.
|
|
bool Task::has_definition()
|
|
{
|
|
return this->defined;
|
|
}
|
|
|
|
/// Task::execute - execute a task's unit definition.
|
|
/// See the design document for what flow control needs to look like here.
|
|
/// \param verbose - Verbosity level - not implemented yet.
|
|
void Task::execute( Conf * configuration )
|
|
{
|
|
// DUFFING - If rex is broken it's probably going to be in this block.
|
|
// Somebody come clean this up, eh?
|
|
|
|
// PREWORK
|
|
// throw if unit not coupled to all necessary values since Task is stateful (yes, stateful is okay)
|
|
std::ostringstream infostring;
|
|
if ( ! this->has_definition() )
|
|
{
|
|
throw Task_NotReady();
|
|
}
|
|
|
|
// get the name
|
|
std::string task_name = this->definition.get_name();
|
|
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Using unit definition: \"" + task_name + "\"." );
|
|
// END PREWORK
|
|
|
|
// get the target execution command
|
|
std::string target_command = configuration->get_execution_context() + "/" + this->definition.get_target();
|
|
|
|
// check if context override
|
|
if ( configuration->has_context_override() )
|
|
{
|
|
// if so, set the CWD.
|
|
chdir( configuration->get_execution_context().c_str() );
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Setting execution context: " + get_working_path() );
|
|
}
|
|
|
|
|
|
// a[0] execute target
|
|
// TODO revise variable sourcing strategy
|
|
// ....sourcing on the shell for variables and environment population doesn't have a good smell.
|
|
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Executing target: \"" + target_command + "\"." );
|
|
|
|
std::string delimiter = " ";
|
|
int space_index = target_command.find( delimiter );
|
|
std::string bin_path = target_command.substr( 0, space_index );
|
|
|
|
if ( exists( bin_path ) )
|
|
{
|
|
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Target executable found.");
|
|
} else {
|
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Target executable does not exist." );
|
|
throw Task_NotReady();
|
|
}
|
|
std::string static_env_file = configuration->get_execution_context() + "/" + this->definition.get_env_vars_file();
|
|
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Vars file: " + static_env_file );
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Shell: " + this->definition.get_shell() );
|
|
|
|
|
|
int return_code = Sproc::execute(
|
|
this->definition.get_shell(),
|
|
static_env_file,
|
|
this->definition.get_user(),
|
|
this->definition.get_group(),
|
|
target_command,
|
|
this->LOG_LEVEL,
|
|
task_name,
|
|
this->definition.get_stdout_log_flag(),
|
|
configuration->get_logs_path()
|
|
);
|
|
|
|
// **********************************************
|
|
// d[0] Error Code Check
|
|
// **********************************************
|
|
if ( return_code == 0 )
|
|
{
|
|
// d[0].0 ZERO
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Target succeeded. Marking as complete." );
|
|
|
|
this->mark_complete();
|
|
|
|
// a[1] NEXT
|
|
return;
|
|
}
|
|
|
|
if ( return_code != 0 )
|
|
{
|
|
// d[0].1 NON-ZERO
|
|
this->slog.log( E_WARN, "[ '" + task_name + "' ] Target failed with exit code " + std::to_string( return_code ) + "." );
|
|
|
|
// **********************************************
|
|
// d[1] Rectify Check
|
|
// **********************************************
|
|
if (! this->definition.get_rectify() )
|
|
{
|
|
// d[1].0 FALSE
|
|
|
|
// **********************************************
|
|
// d[2] Required Check
|
|
// **********************************************
|
|
if (! this->definition.get_required() )
|
|
{
|
|
// d[2].0 FALSE
|
|
// a[2] NEXT
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] This task is not required to continue the plan. Moving on." );
|
|
return;
|
|
} else {
|
|
// d[2].1 TRUE
|
|
// a[3] EXCEPTION
|
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, and failed, and rectification is not enabled." );
|
|
throw TaskException( "Task failed: " + task_name );
|
|
}
|
|
// **********************************************
|
|
// end - d[2] Required Check
|
|
// **********************************************
|
|
}
|
|
|
|
|
|
if ( this->definition.get_rectify() )
|
|
{
|
|
// d[1].1 TRUE (Rectify Check)
|
|
this->slog.log( E_INFO, "[ " + task_name + " ] Rectification pattern is enabled." );
|
|
|
|
// a[4] Execute RECTIFIER
|
|
std::string rectifier_command = this->definition.get_rectifier();
|
|
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Executing rectification: " + rectifier_command + "." );
|
|
|
|
int rectifier_error = Sproc::execute(
|
|
this->definition.get_shell(),
|
|
static_env_file,
|
|
this->definition.get_user(),
|
|
this->definition.get_group(),
|
|
rectifier_command,
|
|
this->LOG_LEVEL,
|
|
task_name,
|
|
this->definition.get_stdout_log_flag(),
|
|
configuration->get_logs_path()
|
|
);
|
|
|
|
// **********************************************
|
|
// d[3] Error Code Check for Rectifier
|
|
// **********************************************
|
|
if ( rectifier_error != 0 )
|
|
{
|
|
// d[3].1 Non-Zero
|
|
this->slog.log( E_WARN, "[ '" + task_name + "' ] Rectification failed with exit code " + std::to_string( rectifier_error ) + "." );
|
|
|
|
// **********************************************
|
|
// d[4] Required Check
|
|
// **********************************************
|
|
if ( ! this->definition.get_required() ) {
|
|
// d[4].0 FALSE
|
|
// a[5] NEXT
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] This task is not required to continue the plan. Moving on." );
|
|
return;
|
|
}
|
|
|
|
if ( this->definition.get_required() )
|
|
{
|
|
// d[4].1 TRUE
|
|
// a[6] EXCEPTION
|
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, but failed, and rectification failed. Lost cause." );
|
|
throw TaskException( "Lost cause, task failure." );
|
|
}
|
|
// **********************************************
|
|
// end - d[4] Required Check
|
|
// **********************************************
|
|
}
|
|
|
|
// d[3] check exit code of rectifier
|
|
if ( rectifier_error == 0 )
|
|
{
|
|
// d[3].0 Zero
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Rectification returned successfully." );
|
|
|
|
// a[7] Re-execute Target
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Re-Executing target \"" + this->definition.get_target() + "\"." );
|
|
|
|
int retry_code = Sproc::execute(
|
|
this->definition.get_shell(),
|
|
static_env_file,
|
|
this->definition.get_user(),
|
|
this->definition.get_group(),
|
|
target_command,
|
|
this->LOG_LEVEL,
|
|
task_name,
|
|
this->definition.get_stdout_log_flag(),
|
|
configuration->get_logs_path()
|
|
);
|
|
|
|
// **********************************************
|
|
// d[5] Error Code Check
|
|
// **********************************************
|
|
if ( retry_code == 0 )
|
|
{
|
|
// d[5].0 ZERO
|
|
// a[8] NEXT
|
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Re-execution was successful." );
|
|
return;
|
|
} else {
|
|
// d[5].1 NON-ZERO
|
|
this->slog.log( E_WARN, "[ '" + task_name + "' ] Re-execution failed with exit code " + std::to_string( retry_code ) + "." );
|
|
|
|
// **********************************************
|
|
// d[6] Required Check
|
|
// **********************************************
|
|
if ( ! this->definition.get_required() )
|
|
{
|
|
// d[6].0 FALSE
|
|
// a[9] NEXT
|
|
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
|
return;
|
|
}
|
|
|
|
if ( this->definition.get_required() )
|
|
{
|
|
// d[6].1 TRUE
|
|
// a[10] EXCEPTION
|
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan." );
|
|
throw TaskException( "Lost cause, task failure." );
|
|
}
|
|
// **********************************************
|
|
// end - d[6] Required Check
|
|
// **********************************************
|
|
}
|
|
|
|
}
|
|
}
|
|
// **********************************************
|
|
// end d[1] Rectify Check
|
|
// **********************************************
|
|
}
|
|
}
|