rex/src/loaders/abstract/Task.cpp

348 lines
12 KiB
C++
Raw Normal View History

2017-12-04 05:46:34 +00:00
/*
Examplar - An automation and testing framework.
© SURRO INDUSTRIES and Chris Punches, 2017.
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"
2020-06-21 00:09:32 +00:00
2017-06-25 06:28:22 +00:00
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
class Task_InvalidDataStructure: public std::runtime_error {
public:
2017-06-25 06:28:22 +00:00
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 access a unit of a Task that is not 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_;
};
2017-06-25 06:28:22 +00:00
/// 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.
2020-06-21 00:09:32 +00:00
Task::Task( int LOG_LEVEL ):
slog( LOG_LEVEL, "e_task" ),
2020-06-21 00:09:32 +00:00
definition( LOG_LEVEL )
{
2017-06-25 22:58:05 +00:00
// it hasn't executed yet.
this->complete = false;
// it hasn't been matched with a definition yet.
this->defined = false;
2020-06-21 00:09:32 +00:00
this->LOG_LEVEL = LOG_LEVEL;
2017-06-25 22:58:05 +00:00
}
2017-06-25 18:07:10 +00:00
/// Task::load_root() - loads json values to private members
///
2017-06-25 21:56:26 +00:00
/// \param loader_root - the Json::Value to populate from.
/// \param verbose - Whether to print verbose information to STDOUT.
2020-06-21 00:09:32 +00:00
void Task::load_root(Json::Value loader_root )
{
if ( loader_root.isMember("name") ) {
2017-06-25 06:28:22 +00:00
this->name = loader_root.get("name", "?").asString();
}
else {
2017-06-25 06:28:22 +00:00
throw Task_InvalidDataStructure();
}
2017-06-25 21:36:32 +00:00
// 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++ ) {
2017-06-25 21:36:32 +00:00
// add each string to dependencies
2020-06-21 00:09:32 +00:00
if ( des_dep_root[i].asString() != "" )
{
2017-06-25 21:36:32 +00:00
this->dependencies.push_back( des_dep_root[i].asString() );
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Added dependency \"" + des_dep_root[i].asString() + "\" to task \"" + this->get_name() + "\"." );
2017-06-25 21:36:32 +00:00
}
}
}
2017-06-25 22:39:41 +00:00
/// Task::get_name - Retrieves the name of the current Task.
2017-06-25 06:28:22 +00:00
std::string Task::get_name()
{
2017-06-25 06:28:22 +00:00
return this->name;
}
2017-06-25 22:39:41 +00:00
/// 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.
2020-06-21 00:09:32 +00:00
void Task::load_definition( Unit selected_unit )
2017-06-25 22:39:41 +00:00
{
this->definition = selected_unit;
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" for task \"" + this->get_name() + "\".");
2017-06-25 22:58:05 +00:00
this->defined = true;
}
/// Task::is_complete - Indicator if the task executed successfully.
bool Task::is_complete()
{
2017-06-25 22:58:05 +00:00
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;
}
2017-06-25 22:58:05 +00:00
/// Task::has_definition - Indicator if the task has attached its definition from a Suite.
bool Task::has_definition()
{
2017-06-25 22:58:05 +00:00
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.
2020-06-21 00:09:32 +00:00
void Task::execute( Conf * configuration )
{
// DUFFING - If Examplar 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)
2017-12-05 11:10:05 +00:00
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, "Using unit: \"" + task_name + "\"." );
// END PREWORK
// get the target execution command
2020-06-21 03:05:51 +00:00
std::string target_command = configuration->get_execution_context() + + "/" this->definition.get_target();
2020-06-21 00:09:32 +00:00
// check if context override
if ( configuration->has_context_override() )
{
2020-06-21 00:09:32 +00:00
// if so, set the CWD.
chdir( configuration->get_execution_context().c_str() );
this->slog.log( E_INFO, "Setting execution context: " + get_working_path() );
}
2020-06-21 00:09:32 +00:00
// a[0] execute target
2018-02-25 06:24:59 +00:00
// TODO revise variable sourcing strategy
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Executing target: \"" + target_command + "\"." );
2020-06-21 03:00:55 +00:00
this->slog.log( E_DEBUG, "Vars file: " + configuration->get_env_vars_file() );
2018-02-25 06:24:59 +00:00
int return_code = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + target_command );
// **********************************************
// d[0] Error Code Check
// **********************************************
if ( return_code == 0 )
{
// d[0].0 ZERO
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Target \"" + task_name + "\" succeeded. Marking as complete." );
this->mark_complete();
// a[1] NEXT
return;
}
if ( return_code != 0 )
{
// d[0].1 NON-ZERO
2020-06-21 00:09:32 +00:00
this->slog.log( E_WARN, "Target \"" + task_name + "\" 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
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
return;
2020-06-21 00:09:32 +00:00
} else {
// d[2].1 TRUE
// a[3] EXCEPTION
2020-06-21 00:09:32 +00:00
this->slog.log( E_FATAL, "Task \"" + task_name + "\" 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)
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Rectification pattern is enabled for \"" + task_name + "\"." );
// a[4] Execute RECTIFIER
std::string rectifier_command = this->definition.get_rectifier();
2017-12-05 11:10:05 +00:00
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Executing rectification: " + rectifier_command + "." );
2017-12-05 11:10:05 +00:00
int rectifier_error = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + rectifier_command );
// **********************************************
// d[3] Error Code Check for Rectifier
// **********************************************
if ( rectifier_error != 0 )
{
// d[3].1 Non-Zero
2020-06-21 00:09:32 +00:00
this->slog.log( E_WARN, "Rectification of \"" + task_name + "\" 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
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
return;
}
if ( this->definition.get_required() )
{
// d[4].1 TRUE
// a[6] EXCEPTION
2020-06-21 00:09:32 +00:00
this->slog.log( E_FATAL, "Task \"" + task_name + "\" is required, it failed, and then 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
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Rectification returned successfully." );
// a[7] Re-execute Target
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Re-Executing target \"" + this->definition.get_target() + "\"." );
2017-12-05 11:10:05 +00:00
int retry_code = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + target_command );
// **********************************************
// d[5] Error Code Check
// **********************************************
if ( retry_code == 0 )
{
// d[5].0 ZERO
// a[8] NEXT
2020-06-21 00:09:32 +00:00
this->slog.log( E_INFO, "Re-execution was successful." );
return;
2020-06-21 00:09:32 +00:00
} else {
// d[5].1 NON-ZERO
2020-06-21 00:09:32 +00:00
this->slog.log( E_WARN, "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
2020-06-21 00:09:32 +00:00
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
2020-06-21 00:09:32 +00:00
this->slog.log( E_FATAL, "Task \"" + task_name + "\" 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
// **********************************************
2017-07-01 20:29:27 +00:00
}
}