rex/src/loaders/abstract/Plan.cpp

277 lines
9.3 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 "Plan.h"
/// Plan_InvalidTaskIndex - Exception thrown when a Plan tries to access a contained Task's value by index not present
/// in the Unit.
class Plan_InvalidTaskIndex: public std::runtime_error { public:
Plan_InvalidTaskIndex(): std::runtime_error("Plan: Attempted to access a Task using an invalid index.") {}
};
/// Plan_InvalidTaskName - Exception thrown when a Plan tries to access a contained Task's value by name not present
/// in the Unit.
class Plan_InvalidTaskName: public std::runtime_error { public:
Plan_InvalidTaskName(): std::runtime_error("Plan: Attempted to access a Task using an invalid name.") {}
};
/// Plan_Task_GeneralExecutionException - Wrapper exception to catch exceptions thrown by the execution of Tasks.
class Plan_Task_GeneralExecutionException: 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 Plan_Task_GeneralExecutionException(const char* message):
msg_(message)
{
}
/** Constructor (C++ STL strings).
* @param message The error message.
*/
explicit Plan_Task_GeneralExecutionException(const std::string& message):
msg_(message)
{}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Plan_Task_GeneralExecutionException() 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_;
};
/// Plan_Task_Missing_Dependency - Exception thrown when a Plan tries to access a contained Task's value by name not present
/// in the Unit.
class Plan_Task_Missing_Dependency: 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 Plan_Task_Missing_Dependency(const char* message):
msg_(message)
{
}
/** Constructor (C++ STL strings).
* @param message The error message.
*/
explicit Plan_Task_Missing_Dependency(const std::string& message):
msg_(message)
{}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Plan_Task_Missing_Dependency() 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_;
};
/// Plan::Plan() - Constructor for Plan class. A Plan is a managed container for a Task vector. These tasks reference
/// Units that are defined in the Units files (Suite). If Units are definitions, Tasks are selections of those
/// definitions to execute, and if Units together form a Suite, Tasks together form a Plan.
Plan::Plan(Conf * configuration, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_plan_" )
{
this->configuration = configuration;
this->LOG_LEVEL = LOG_LEVEL;
}
/// Plan::load_plan_file - Uses the json_root buffer on each run to append intact Units as they're deserialized from
/// the provided file.
///
/// \param filename - The filename to load the plan from.
/// \param verbose - Whether to print verbose output to STDOUT.
void Plan::load_plan_file( std::string filename )
{
// plan always loads from file
this->load_json_file( filename );
// staging buffer
Json::Value jbuff;
// fill the jbuff staging buffer wih a json::value object in the supplied filename
if ( this->get_serialized( jbuff, "plan" ) == 0 )
{
this->json_root = jbuff;
}
// iterate through the json::value members that have been loaded. append to this->tasks vector
// buffer for tasks to append:
Task tmp_T = Task( this->LOG_LEVEL );
for ( int index = 0; index < this->json_root.size(); index++ )
{
tmp_T.load_root( this->json_root[ index ] );
this->tasks.push_back( tmp_T );
this->slog.log( LOG_INFO, "Added task \"" + tmp_T.get_name() + "\" to Plan." );
}
}
/// Plan::get_task - Retrieves a task by index.
///
/// \param result - The variable receiving the value.
/// \param index - The numerical index in the Task vector to retrieve a value for.
/// \param verbose - Whether to print verbose output to STDOUT.
void Plan::get_task(Task & result, int index )
{
if ( index <= this->tasks.size() )
{
result = this->tasks[ index ];
} else {
throw Plan_InvalidTaskIndex();
}
}
/// Plan::load_definitions - Load the units corresponding to each task in plan from the given Suite.
///
/// \param unit_definitions - The Suite to load definitions from.
/// \param verbose - Whether to print verbose information to STDOUT.
void Plan::load_definitions( Suite unit_definitions )
{
// placeholder Unit
Unit tmp_U = Unit( this->LOG_LEVEL );
// for every task in the plan:
for (int i = 0; i < this->tasks.size(); i++ )
{
// load the tmp_U corresponding to that task name
unit_definitions.get_unit( tmp_U, this->tasks[i].get_name() );
// then have that task attach a copy of tmp_U
this->tasks[i].load_definition( tmp_U );
}
}
/// Plan::get_task - Retrieves a task by name.
///
/// \param result - The variable receiving the value.
/// \param provided_name - The name to find a task by.
/// \param verbose - Whether to print verbose output to STDOUT.
void Plan::get_task(Task & result, std::string provided_name )
{
bool foundMatch = false;
for ( int i = 0; i < this->tasks.size(); i++ )
{
if ( this->tasks[i].get_name() == provided_name )
{
result = this->tasks[i];
foundMatch = true;
break;
}
}
if (! foundMatch )
{
this->slog.log( E_FATAL, "Task name \"" + provided_name + "\" was referenced but not defined!" );
throw Plan_InvalidTaskName();
}
}
/// Plan::all_dependencies_complete
///
/// \param name - The name of the task in the plan to check met dependencies for.
/// \return - boolean representation of whether all dependencies are complete or not.
bool Plan::all_dependencies_complete(std::string name)
{
// get the task by name
Task named_task = Task( this->LOG_LEVEL );
this->get_task( named_task, name );
// get the dependencies of that task
std::vector<std::string> deps = named_task.get_dependencies();
// create an empty task to assign values to during iteration
Task tmpTask = Task( this->LOG_LEVEL );
// iterate through its dependencies
for ( int i = 0; i < deps.size(); i++ )
{
this->get_task( tmpTask, deps[i]);
if (! tmpTask.is_complete() )
{
// error message?
return false;
}
}
return true;
}
/// Plan::execute() - Iterates through all tasks in a plan and executes them.
///
/// \param verbose
void Plan::execute()
{
// for each task in this plan
for ( int i = 0; i < this->tasks.size(); i++ )
{
if (this->all_dependencies_complete(this->tasks[i].get_name()) )
{
this->slog.log( E_INFO, "[ '" + this->tasks[i].get_name() + "' ] Executing..." );
try {
this->tasks[i].execute( this->configuration );
}
catch (std::exception& e) {
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] Report: " + e.what() );
throw Plan_Task_GeneralExecutionException("Could not execute task.");
}
} else {
// not all deps met for this task
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] This task was specified in the Plan but not executed due to missing dependencies. Please revise your plan." );
throw Plan_Task_Missing_Dependency( "Unmet dependency for task." );
}
}
}