rex/src/loaders/abstract/Task.cpp

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
// **********************************************
}
}