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/>.
*/
2017-04-30 05:39:03 +00:00
# include "Task.h"
2020-06-21 00:09:32 +00:00
2017-04-30 05:39:03 +00:00
2017-06-25 06:28:22 +00:00
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
2017-12-02 09:22:09 +00:00
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. " ) { }
} ;
2017-06-09 03:49:13 +00:00
2017-06-27 02:27:24 +00:00
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
2017-12-02 09:22:09 +00:00
class Task_NotReady : public std : : runtime_error {
public :
2017-06-27 02:27:24 +00:00
Task_NotReady ( ) : std : : runtime_error ( " Task: Attempted to access a unit of a Task that is not defined. " ) { }
} ;
2017-12-02 02:41:22 +00:00
2017-12-05 04:25:48 +00:00
/// Task_RequiredButFailedTask - Exception thrown when a Task fails but should not.
class TaskException : public std : : exception
{
2017-12-02 09:22:09 +00:00
public :
2017-12-05 04:25:48 +00:00
/** 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-12-02 02:41:22 +00:00
} ;
2017-06-27 02:27:24 +00:00
2017-12-05 04:25:48 +00:00
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 ) :
2020-06-21 01:10:42 +00:00
slog ( LOG_LEVEL , " e_task " ) ,
2020-06-21 00:09:32 +00:00
definition ( LOG_LEVEL )
2017-12-02 09:22:09 +00:00
{
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-09 03:49:13 +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 )
2017-04-30 05:39:03 +00:00
{
2017-12-02 09:22:09 +00:00
if ( loader_root . isMember ( " name " ) ) {
2017-06-25 06:28:22 +00:00
this - > name = loader_root . get ( " name " , " ? " ) . asString ( ) ;
2017-12-02 09:22:09 +00:00
}
else {
2017-06-25 06:28:22 +00:00
throw Task_InvalidDataStructure ( ) ;
}
2017-04-30 05:39:03 +00:00
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
2017-12-02 09:22:09 +00:00
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-09 03:49:13 +00:00
}
2017-04-30 05:39:03 +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-09 03:49:13 +00:00
{
2017-06-25 06:28:22 +00:00
return this - > name ;
2017-06-09 03:49:13 +00:00
}
2017-04-30 05:39:03 +00:00
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.
2017-12-02 09:22:09 +00:00
bool Task : : is_complete ( )
{
2017-06-25 22:58:05 +00:00
return this - > complete ;
}
2017-12-03 19:10:09 +00:00
/// 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.
2017-12-02 09:22:09 +00:00
bool Task : : has_definition ( )
{
2017-06-25 22:58:05 +00:00
return this - > defined ;
2017-06-27 02:27:24 +00:00
}
/// Task::execute - execute a task's unit definition.
2017-12-02 02:41:22 +00:00
/// 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 )
2017-06-27 02:27:24 +00:00
{
2017-12-03 19:10:09 +00:00
// DUFFING - If Examplar is broken it's probably going to be in this block.
2017-12-05 04:29:07 +00:00
// Somebody come clean this up, eh?
2017-12-02 02:41:22 +00:00
// PREWORK
2017-12-03 19:10:09 +00:00
// 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 ;
2017-12-05 04:25:48 +00:00
if ( ! this - > has_definition ( ) )
{
2017-12-02 09:22:09 +00:00
throw Task_NotReady ( ) ;
}
2017-06-27 02:27:24 +00:00
2017-12-02 02:41:22 +00:00
// get the name
std : : string task_name = this - > definition . get_name ( ) ;
2020-06-21 01:10:42 +00:00
this - > slog . log ( E_DEBUG , " Using unit: \" " + task_name + " \" . " ) ;
2017-12-02 02:41:22 +00:00
// END PREWORK
// get the target execution command
std : : string target_command = this - > definition . get_target ( ) ;
2020-06-21 00:09:32 +00:00
// check if context override
if ( configuration - > has_context_override ( ) )
2017-12-05 04:25:48 +00:00
{
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 ( ) ) ;
2017-06-27 02:27:24 +00:00
}
2020-06-21 00:09:32 +00:00
2017-12-05 04:25:48 +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_DEBUG , " Loading environment variable file: " + configuration - > get_env_vars_file ( ) ) ;
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 ) ;
2017-06-27 02:27:24 +00:00
2017-12-05 04:25:48 +00:00
// **********************************************
// 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. " ) ;
2017-12-05 04:25:48 +00:00
2017-12-03 19:10:09 +00:00
this - > mark_complete ( ) ;
2017-12-05 04:25:48 +00:00
// 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 ) + " . " ) ;
2017-12-01 06:13:30 +00:00
2017-12-05 04:25:48 +00:00
// **********************************************
// 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. " ) ;
2017-12-05 04:25:48 +00:00
return ;
2020-06-21 00:09:32 +00:00
} else {
2017-12-05 04:25:48 +00:00
// 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 ) ;
2017-12-05 04:25:48 +00:00
}
// **********************************************
// 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 + " \" . " ) ;
2017-12-05 04:25:48 +00:00
// a[4] Execute RECTIFIER
2017-12-02 02:41:22 +00:00
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
2018-06-02 03:19:30 +00:00
int rectifier_error = Sproc : : execute ( " source " + configuration - > get_env_vars_file ( ) + " && " + rectifier_command ) ;
2017-12-02 02:41:22 +00:00
2017-12-05 04:25:48 +00:00
// **********************************************
// 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 ) + " . " ) ;
2017-12-04 02:15:41 +00:00
2017-12-05 04:25:48 +00:00
// **********************************************
// 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. " ) ;
2017-12-05 04:25:48 +00:00
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. " ) ;
2017-12-02 02:41:22 +00:00
}
2017-12-05 04:25:48 +00:00
// **********************************************
// end - d[4] Required Check
// **********************************************
2017-12-02 09:22:09 +00:00
}
2017-12-05 04:25:48 +00:00
// 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. " ) ;
2017-12-05 04:25:48 +00:00
// 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
2018-06-02 03:23:38 +00:00
int retry_code = Sproc : : execute ( " source " + configuration - > get_env_vars_file ( ) + " && " + target_command ) ;
2017-12-05 04:25:48 +00:00
// **********************************************
// 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. " ) ;
2017-12-05 04:25:48 +00:00
return ;
2020-06-21 00:09:32 +00:00
} else {
2017-12-05 04:25:48 +00:00
// 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 ) + " . " ) ;
2017-12-05 04:25:48 +00:00
// **********************************************
// 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. " ) ;
2017-12-05 04:25:48 +00:00
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. " ) ;
2017-12-05 04:25:48 +00:00
}
// **********************************************
// end - d[6] Required Check
// **********************************************
}
2017-12-02 09:22:09 +00:00
2017-12-02 02:41:22 +00:00
}
2017-12-01 06:13:30 +00:00
}
2017-12-05 04:25:48 +00:00
// **********************************************
// end d[1] Rectify Check
// **********************************************
2017-07-01 20:29:27 +00:00
}
2018-06-02 03:19:30 +00:00
}