6 Commits

7 changed files with 470 additions and 107 deletions

View File

@@ -2,10 +2,10 @@
"units": [ "units": [
{ {
"name": "independent test 1", "name": "independent test 1",
"target": "/usr/bin/false", "target": "/usr/bin/true",
"rectifier": "/usr/bin/true", "rectifier": "/usr/bin/true",
"active": true, "active": true,
"required": false, "required": true,
"rectify": true "rectify": true
}, },
{ {
@@ -26,10 +26,10 @@
}, },
{ {
"name": "dependent test", "name": "dependent test",
"target": "/usr/bin/true", "target": "/usr/bin/false",
"rectifier": "/usr/bin/false", "rectifier": "/usr/bin/true",
"active": true, "active": true,
"required": false, "required": true,
"rectify": true "rectify": true
} }
] ]

View File

@@ -0,0 +1,60 @@
# Logic Tree for Examplar Task Execution
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/>.
## Key:
-Actions are labled by indices in a zero-indexed array 'a'.
-Decisions are labeled by indices in a zero-indexed array 'd'.
-Decisions have two possible states, TRUE or FALSE (0 or 1 respectively).
-Decision determinations are labeled with the decision label and a subspecifier.
## Diagram:
a[0] Execute Target
d[0] Error Code Check
d[0].0 ZERO
a[1] NEXT
d[0].1 NON-ZERO
d[1] Rectify Check
d[1].0 FALSE
d[2] Required Check
d[2].0 FALSE
a[2] NEXT
d[2].1 TRUE
a[3] EXCEPTION
d[1].1 TRUE
a[4] Execute Rectifier
d[3] Error Code Check
d[3].1 NON-ZERO
d[4] Required Check
d[4].0 FALSE
a[5] NEXT
d[4].1 TRUE
a[6] EXCEPTION
d[3].0 ZERO
a[7] Re-Execute Target
d[5] Error Code Check
d[5].0 ZERO
a[8] NEXT
d[5].1 NON-ZERO
d[6] Required Check
d[6].0 FALSE
a[9] NEXT
d[6].1 TRUE
a[10] EXCEPTION

View File

@@ -17,10 +17,17 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <iostream> #include <iostream>
#include "src/json/json.h" #include "src/json/json.h"
#include "src/loaders/loaders.h" #include "src/loaders/loaders.h"
/*
* TODO Logging -- Pump to syslog with clone to STDOUT
* TODO Unit Files Directory instead of a single Unit File (optional to user)
* TODO Commandline switches
*/
int main( ) int main( )
{ {
@@ -43,11 +50,15 @@ int main( )
std::cout << "Ready to execute all tasks in Plan." << std::endl; std::cout << "Ready to execute all tasks in Plan." << std::endl;
try { try
{
plan.execute( verbose ); plan.execute( verbose );
} }
catch ( std::exception& e) {
catch ( std::exception& e)
{
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
return 1;
} }
return 0; return 0;

View File

@@ -19,13 +19,52 @@
*/ */
#include <string.h> #include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include "Suite.h" #include "Suite.h"
#include <dirent.h>
/// Suite_InvalidUnitMember - Exception thrown when a Suite tries to access a contained Unit's value that is not /// Suite_InvalidUnitMember - Exception thrown when a Suite tries to access a contained Unit's value that is not
/// present in the Unit. /// present in the Unit.
class Suite_InvalidUnitMember: public std::runtime_error { public: class SuiteException: public std::exception
// TODO rework this to accept the name of the member not able to be fetched. {
Suite_InvalidUnitMember(): std::runtime_error("Suite: Attempted to access a member of a Unit that is not set.") {} 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 SuiteException(const char* message):
msg_(message)
{
}
/** Constructor (C++ STL strings).
* @param message The error message.
*/
explicit SuiteException(const std::string& message):
msg_(message)
{}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~SuiteException() 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_;
}; };
@@ -42,40 +81,115 @@ class Suite_InvalidUnitMember: public std::runtime_error { public:
/// if the implementor so desires. /// if the implementor so desires.
Suite::Suite(): JSON_Loader() {}; Suite::Suite(): JSON_Loader() {};
bool is_file( std::string path)
{
struct stat buf;
stat( path.c_str(), &buf );
return S_ISREG(buf.st_mode);
}
bool is_dir( std::string path )
{
struct stat buf;
stat( path.c_str(), &buf );
return S_ISDIR(buf.st_mode);
}
void get_units_from_dir( std::vector<std::string> * files, std::string path )
{
DIR* dirFile = opendir( path.c_str() );
if ( dirFile )
{
std::string entry;
struct dirent* hFile;
errno = 0;
while (( hFile = readdir( dirFile )) != NULL )
{
if ( !strcmp( hFile->d_name, "." )) continue;
if ( !strcmp( hFile->d_name, ".." )) continue;
// hidden files
if ( hFile->d_name[0] == '.' ) continue;
// dirFile.name is the name of the file. Do whatever string comparison
// you want here. Something like:
if ( strstr( hFile->d_name, ".units" ))
{
std::string full_path = path + hFile->d_name;
files->push_back( full_path );
}
}
closedir( dirFile );
} else {
std::cout << "file not found" << std::endl;
}
}
/// Suite::load_units_file - Uses the json_root buffer on each run to append intact Units as they're /// Suite::load_units_file - Uses the json_root buffer on each run to append intact Units as they're
/// deserialized from the provided file. /// deserialized from the provided file.
/// ///
/// \param filename - The file to pull the JSON-formatted units from. /// \param units_path - The file to pull the JSON-formatted units from.
/// \param verbose - Whether to print verbose output to STDOUT. /// \param verbose - Whether to print verbose output to STDOUT.
void Suite::load_units_file( std::string filename, bool verbose ) void Suite::load_units_file( std::string units_path, bool verbose )
{ {
// will use json_root buffer on each run to append to this->units vector as valid units are found. std::vector<std::string> unit_files;
this->load_json_file( filename, verbose );
// staging buffer if ( is_dir( units_path ) )
Json::Value jbuff;
// fill the jbuff staging buffer with a json::value object in the supplied filename
if ( this->get_serialized( jbuff, "units", verbose ) == 0)
{ {
this->json_root = jbuff; // we have a directory path. find all files ending in *.units and load them into a vector<std::string>
get_units_from_dir( &unit_files, units_path );
} }
// iterate through the json::value members that have been loaded. append to this->units vector if ( is_file( units_path ) )
// buffer for units to append:
Unit tmp_U;
for ( int index = 0; index < this->json_root.size(); index++ )
{ {
// assemble the unit from json_root using the built-in value operator unit_files.push_back( units_path );
tmp_U.load_root( this->json_root[ index ] ); }
if ( tmp_U.get_active() ) {
// append to this->units std::ostringstream infostring;
this->units.push_back( tmp_U ); infostring << "Unit files found: " << unit_files.size() << std::endl;
if ( verbose ) { syslog(LOG_INFO, infostring.str().c_str() );
std::cout << "Added unit \"" << tmp_U.get_name() << "\" to Suite." << std::endl; std::cerr << infostring.str();
for ( int i = 0; i < unit_files.size(); i++ )
{
// will use json_root buffer on each run to append to this->units vector as valid units are found.
this->load_json_file( unit_files[i], verbose );
// staging buffer
Json::Value jbuff;
// fill the jbuff staging buffer with a json::value object in the supplied units_path
if ( this->get_serialized( jbuff, "units", verbose ) == 0)
{
this->json_root = jbuff;
}
// iterate through the json::value members that have been loaded. append to this->units vector
// buffer for units to append:
Unit tmp_U;
for ( int index = 0; index < this->json_root.size(); index++ )
{
// assemble the unit from json_root using the built-in value operator
tmp_U.load_root( this->json_root[ index ] );
if ( tmp_U.get_active() ) {
// append to this->units
this->units.push_back( tmp_U );
if ( verbose ) {
std::ostringstream infostring;
infostring << "Added unit \"" << tmp_U.get_name() << "\" to Suite." << std::endl;
syslog(LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
}
} }
} }
} }
} }
/// Suite::get_unit - returns a contained Unit identified by name attribute. /// Suite::get_unit - returns a contained Unit identified by name attribute.
@@ -99,8 +213,11 @@ void Suite::get_unit(Unit & result, std::string provided_name)
if (! foundMatch ) if (! foundMatch )
{ {
std::cerr << "Unit name \"" << provided_name << "\" was referenced but not defined!" << std::endl; std::ostringstream infostring;
throw Suite_InvalidUnitMember(); infostring << "Unit name \"" << provided_name << "\" was referenced but not defined!" << std::endl;
syslog(LOG_ERR, infostring.str().c_str() );
std::cerr << infostring.str();
throw SuiteException( infostring.str() );
} }
} }

View File

@@ -27,8 +27,6 @@
#include "Unit.h" #include "Unit.h"
class Suite: public JSON_Loader class Suite: public JSON_Loader
{ {
protected: protected:

View File

@@ -21,6 +21,7 @@
#include "Task.h" #include "Task.h"
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <syslog.h>
#include "../sproc/Sproc.h" #include "../sproc/Sproc.h"
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON. /// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
@@ -35,18 +36,49 @@ public:
Task_NotReady(): std::runtime_error("Task: Attempted to access a unit of a Task that is not defined.") {} 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 is failed but required, and rectification also failed.
class Task_RequiredButFailedTask: public std::runtime_error { /// Task_RequiredButFailedTask - Exception thrown when a Task fails but should not.
class TaskException: public std::exception
{
public: public:
Task_RequiredButFailedTask(): std::runtime_error("Task: Attempted to execute a Task that failed and was required.") {} /** 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_RequiredButFailedTask - Exception thrown when a Task is failed but required, and rectification also failed but
/// returned with a zero exit code (dont try to fool the check).
class Task_RequiredButRectifierDoesNotHeal: public std::runtime_error {
public:
Task_RequiredButRectifierDoesNotHeal(): std::runtime_error("Task: The rectification script was executed and reported success, but did not actually heal the faulty condition of the Task target.") {}
};
/// Task::Task() - Constructor for the Task class. The Task is the building block of a Plan indicating of which Unit to /// 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. /// execute, and its dependencies on other units to have already been completed successfully.
@@ -81,8 +113,12 @@ void Task::load_root(Json::Value loader_root, bool verbose )
if ( des_dep_root[i].asString() != "" ) { if ( des_dep_root[i].asString() != "" ) {
this->dependencies.push_back( des_dep_root[i].asString() ); this->dependencies.push_back( des_dep_root[i].asString() );
if ( verbose ) { if ( verbose ) {
std::cout << "Added dependency \"" << des_dep_root[i].asString() std::ostringstream infostring;
<< "\" to task \"" << this->get_name() << "\"." << std::endl; infostring << "Added dependency \"" << des_dep_root[i].asString() << "\" to task \""
<< this->get_name() << "\"." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
} }
} }
} }
@@ -102,8 +138,12 @@ void Task::load_definition( Unit selected_unit, bool verbose )
{ {
this->definition = selected_unit; this->definition = selected_unit;
if ( verbose ) { if ( verbose ) {
std::cout << "Loaded definition \"" << selected_unit.get_name() << "\" for task \"" std::ostringstream infostring;
<< this->get_name() << "\"." << std::endl; infostring << "Loaded definition \"" << selected_unit.get_name() << "\" for task \""
<< this->get_name() << "\"." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
} }
this->defined = true; this->defined = true;
} }
@@ -134,16 +174,24 @@ bool Task::has_definition()
return this->defined; return this->defined;
} }
/// Task::execute - execute a task's unit definition. /// Task::execute - execute a task's unit definition.
/// See the design document for what flow control needs to look like here. /// See the design document for what flow control needs to look like here.
/// \param verbose - Verbosity level - not implemented yet. /// \param verbose - Verbosity level - not implemented yet.
void Task::execute( bool verbose ) void Task::execute( bool verbose )
{ {
// DUFFING - If Examplar is broken it's probably going to be in this block. // DUFFING - If Examplar is broken it's probably going to be in this block.
// Somebody come clean this up, eh?
// PREWORK // PREWORK
// throw if unit not coupled to all necessary values since Task is stateful (yes, stateful is okay) // throw if unit not coupled to all necessary values since Task is stateful (yes, stateful is okay)
if (! this->has_definition() ) { std::ostringstream infostring;
if ( ! this->has_definition() )
{
throw Task_NotReady(); throw Task_NotReady();
} }
@@ -156,81 +204,209 @@ void Task::execute( bool verbose )
std::string target_command = this->definition.get_target(); std::string target_command = this->definition.get_target();
// if we're in verbose mode, do some verbose things // if we're in verbose mode, do some verbose things
if ( verbose ) { if ( verbose )
std::cout << "\tUsing unit \"" << task_name << "\"." << std::endl; {
std::cout << "\tExecuting target \"" << target_command << "\"." << std::endl; infostring = std::ostringstream();
infostring << "\tUsing unit \"" << task_name << "\"." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
infostring = std::ostringstream();
infostring << "\tExecuting target \"" << target_command << "\"." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
} }
// execute target // a[0] execute target
int return_code = Sproc::execute( target_command ); int return_code = Sproc::execute( target_command );
// d[0] check exit code of target // **********************************************
if (return_code == 0) { // d[0] Error Code Check
// Zero d[0] return from target execution, good to return // **********************************************
if ( return_code == 0 )
{
// d[0].0 ZERO
if ( verbose ) { if ( verbose ) {
std::cout << "\tTarget " << task_name << " succeeded." << std::endl; infostring = std::ostringstream();
infostring << "\tTarget " << task_name << " succeeded. Marking as complete." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
} }
this->mark_complete(); this->mark_complete();
// next
} else {
// Non-Zero d[0] from initial target execution, get to d[1]
std::cout << "\tTarget \"" << task_name << "\" failed with exit code " << return_code << "." << std::endl;
// check if rectify pattern is enabled d[1] // a[1] NEXT
if ( this->definition.get_rectify() ) { return;
// yes d[1] }
std::cout << "\tRectification pattern is enabled for \"" << task_name << "\"." << std::endl;
// execute RECTIFIER if ( return_code != 0 )
{
// d[0].1 NON-ZERO
infostring = std::ostringstream();
infostring << "\tTarget \"" << task_name << "\" failed with exit code " << return_code << "." << std::endl;
syslog(LOG_ERR, infostring.str().c_str() );
std::cerr << infostring.str();
// **********************************************
// 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
infostring = std::ostringstream();
infostring << "\tThis task is not required to continue the plan. Moving on." << std::endl;
syslog(LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
return;
}
if ( this->definition.get_required() )
{
// d[2].1 TRUE
// a[3] EXCEPTION
throw TaskException("Task \"" + task_name + "\" is required, and failed, and rectification is not enabled.");
}
// **********************************************
// end - d[2] Required Check
// **********************************************
}
if ( this->definition.get_rectify() )
{
// d[1].1 TRUE (Rectify Check)
infostring = std::ostringstream();
infostring << "\tRectification pattern is enabled for \"" << task_name << "\"." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
// a[4] Execute RECTIFIER
std::string rectifier_command = this->definition.get_rectifier(); std::string rectifier_command = this->definition.get_rectifier();
std::cout << "\tExecuting rectification: " << rectifier_command << "." << std::endl;
infostring = std::ostringstream();
infostring << "\tExecuting rectification: " << rectifier_command << "." << std::endl;
syslog(LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
int rectifier_error = Sproc::execute( rectifier_command ); int rectifier_error = Sproc::execute( rectifier_command );
// d[3] check exit code of rectifier // **********************************************
if (rectifier_error) { // d[3] Error Code Check for Rectifier
//d[3] non-zero // **********************************************
if ( rectifier_error != 0 )
{
// d[3].1 Non-Zero
infostring = std::ostringstream();
infostring << "\tRectification of \"" << task_name << "\" failed with exit code "
<< rectifier_error << "." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << "\tRectification of \"" << task_name << "\" failed with exit code " std::cout << infostring.str();
<< rectifier_error << "." << std::endl;
// d[2] check if REQUIRED // **********************************************
if ( this->definition.get_required() ) { // d[4] Required Check
// d[2] yes // **********************************************
// halt/exception if ( ! this->definition.get_required() ) {
throw Task_RequiredButFailedTask(); // d[4].0 FALSE
// a[5] NEXT
infostring = std::ostringstream();
infostring << "\tThis task is not required to continue the plan. Moving on." << std::endl;
syslog(LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
return;
} }
// d[2] no
// next
}
// d[3] zero
// execute target if ( this->definition.get_required() )
std::cout << "\tRe-Executing target \"" << this->definition.get_target() << "\"." << std::endl; {
int retry_code = Sproc::execute( target_command ); // d[4].1 TRUE
// a[6] EXCEPTION
throw TaskException("Task \"" + task_name + "\" is required, and failed, then rectified but rectification failed.");
}
// **********************************************
// end - d[4] Required Check
// **********************************************
}
// d[3] check exit code of rectifier
if ( rectifier_error == 0 )
{
// d[3].0 Zero
infostring = std::ostringstream();
infostring << "\tRectification returned successfully." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
// a[7] Re-execute Target
infostring = std::ostringstream();
infostring << "\tRe-Executing target \"" << this->definition.get_target() << "\"." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
int retry_code = Sproc::execute( target_command );
// **********************************************
// d[5] Error Code Check
// **********************************************
if ( retry_code == 0 )
{
// d[5].0 ZERO
// a[8] NEXT
infostring = std::ostringstream();
infostring << "\tRe-execution was successful." << std::endl;
syslog( LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
return;
}
if ( retry_code != 0 )
{
// d[5].1 NON-ZERO
infostring = std::ostringstream();
infostring << "\tRe-execution failed with exit code " << retry_code << "." << std::endl;
syslog(LOG_ERR, infostring.str().c_str() );
std::cerr << infostring.str();
// **********************************************
// d[6] Required Check
// **********************************************
if ( ! this->definition.get_required() )
{
// d[6].0 FALSE
// a[9] NEXT
infostring = std::ostringstream();
infostring << "\tThis task is not required to continue the plan. Moving on." << std::endl;
syslog(LOG_INFO, infostring.str().c_str() );
std::cout << infostring.str();
return;
}
if ( this->definition.get_required() )
{
// d[6].1 TRUE
// a[10] EXCEPTION
throw TaskException("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.");
}
// **********************************************
// end - d[6] Required Check
// **********************************************
}
// d[4] exit code of target retry
if (retry_code == 0) {
// d[4] zero
} }
// d[4] non-zero
// d[5] required check
if ( this->definition.get_required() ) {
// d[5] yes
std::cout << "\tTask \"" << task_name << "\" is required but rectification did not heal." << std::endl;
throw Task_RequiredButRectifierDoesNotHeal();
}
// d[5] no
// next
} }
// no d[1] // **********************************************
std::cout << "\tRectification is not enabled for \"" << task_name << "\"." << std::endl; // end d[1] Rectify Check
// required d[2] // **********************************************
if ( this->definition.get_required() ) {
// d[2] yes
// This is executing.....
std::cout << "\tThis task is required to continue the plan." << std::endl;
// but these are NOT executing?????
throw Task_RequiredButFailedTask();
} // d[2] no
std::cout << "\tThis task is not required to continue the plan." << std::endl;
} }
} }

View File

@@ -27,6 +27,7 @@
class Task class Task
{ {
protected: protected:
// the name of this task // the name of this task
std::string name; std::string name;