1
0
Fork 0
arangodb/tests/Maintenance/MaintenanceFeatureTest.cpp

804 lines
28 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief test suite for ClusterComm
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2018 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Kaveh Vahedipour
/// @author Matthew Von-Maszewski
/// @author Copyright 2018, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "catch.hpp"
#include <iostream>
#include <velocypack/vpack.h>
#include <velocypack/velocypack-aliases.h>
#include "ApplicationFeatures/ApplicationServer.h"
#include "Basics/ConditionLocker.h"
#include "Basics/ConditionVariable.h"
#include "Basics/Result.h"
#include "Cluster/Action.h"
#include "Cluster/MaintenanceFeature.h"
//
// structure used to store expected states of action properties
//
struct Expected {
int _id;
int _result;
int _state;
int _progress;
};
typedef std::vector<Expected> ExpectedVec_t;
//
// TestProgressHandler lets us know once ApplicationServer is ready
//
class TestProgressHandler : public arangodb::application_features::ProgressHandler {
public:
TestProgressHandler() {
_serverReady=false;
using std::placeholders::_1;
_state = std::bind(& TestProgressHandler::StateChange, this, _1);
using std::placeholders::_2;
_feature = std::bind(& TestProgressHandler::FeatureChange, this, _1, _2);
}
void StateChange(arangodb::application_features::ServerState newState) {
if (arangodb::application_features::ServerState::IN_WAIT == newState) {
CONDITION_LOCKER(clock, _serverReadyCond);
_serverReady = true;
_serverReadyCond.broadcast();
}
}
void FeatureChange(arangodb::application_features::ServerState newState, std::string const &) {
}
arangodb::basics::ConditionVariable _serverReadyCond;
std::atomic_bool _serverReady;
};// class TestProgressHandler
using namespace arangodb::maintenance;
//
// TestFeature wraps MaintenanceFeature to all test specific action objects
// by overriding the actionFactory() virtual function. Two versions:
// 1. default constructor for non-threaded actions
// 2. constructor with ApplicationServer pointer for threaded actions
//
class TestMaintenanceFeature : public arangodb::MaintenanceFeature {
public:
TestMaintenanceFeature(arangodb::application_features::ApplicationServer& as)
: arangodb::MaintenanceFeature(as) {
// begin with no threads to allow queue validation
_maintenanceThreadsMax = 0;
as.addReporter(_progressHandler);
};
virtual ~TestMaintenanceFeature() {
};
void setSecondsActionsBlock(uint32_t seconds) {_secondsActionsBlock = seconds;};
/// @brief set thread count, then activate the threads via start(). One time usage only.
/// Code waits until background ApplicationServer known to have fully started.
void setMaintenanceThreadsMax(uint32_t threads) {
CONDITION_LOCKER(clock, _progressHandler._serverReadyCond);
while(!_progressHandler._serverReady) {
_progressHandler._serverReadyCond.wait();
} // while
_maintenanceThreadsMax = threads;
start();
} // setMaintenanceThreadsMax
virtual arangodb::Result addAction(
std::shared_ptr<arangodb::maintenance::Action> action, bool executeNow = false) override {
_recentAction = action;
return MaintenanceFeature::addAction(action, executeNow);
}
virtual arangodb::Result addAction(
std::shared_ptr<arangodb::maintenance::ActionDescription> const & description,
bool executeNow = false) override {
return MaintenanceFeature::addAction(description, executeNow);
}
bool verifyRegistryState(ExpectedVec_t & expected) {
bool good(true);
VPackBuilder registryBuilder(toVelocyPack());
VPackArrayIterator registry(registryBuilder.slice());
auto action = registry.begin();
auto check = expected.begin();
for ( ; registry.end() != action && expected.end()!=check; ++action, ++check) {
VPackSlice id = (*action).get("id");
if (!(id.isInteger() && id.getInt() == check->_id)) {
std::cerr << "Id mismatch: action has " << id.getInt()
<< " expected " << check->_id << std::endl;
good = false;
} // if
VPackSlice result = (*action).get("result");
if (!(result.isInteger() && check->_result == result.getInt())) {
std::cerr << "Result mismatch: action has " << result.getInt()
<< " expected " << check->_result << std::endl;
good = false;
} // if
VPackSlice state = (*action).get("state");
if (!(state.isInteger() && check->_state == state.getInt())) {
std::cerr << "State mismatch: action has " << state.getInt()
<< " expected " << check->_state << std::endl;
good = false;
} // if
VPackSlice progress = (*action).get("progress");
if (!(progress.isInteger() && check->_progress == progress.getInt())) {
std::cerr << "Progress mismatch: action has " << progress.getInt()
<< " expected " << check->_progress << std::endl;
good = false;
} // if
} // for
return good;
} // verifyRegistryState
/// @brief poll registry until all actions finish
void waitRegistryComplete() {
bool again;
do {
again = false;
std::this_thread::sleep_for(std::chrono::seconds(1));
VPackBuilder registryBuilder(toVelocyPack());
VPackArrayIterator registry(registryBuilder.slice());
for (auto action : registry) {
VPackSlice state = action.get("state");
again = again || (COMPLETE != state.getInt() && FAILED != state.getInt());
} // for
} while(again);
} // waitRegistryComplete
public:
std::shared_ptr<Action> _recentAction;
TestProgressHandler _progressHandler;
};// TestMaintenanceFeature
//
// TestActionBasic simulates a multistep action by counting down
// on each call to first() and next() until iteration counter is zero.
// Returns false upon reaching zero
//
class TestActionBasic : public ActionBase {
public:
TestActionBasic(
arangodb::MaintenanceFeature& feature, ActionDescription const& description)
: ActionBase(feature, description), _iteration(1), _resultCode(0) {
std::string value, iterate_count;
auto gres = description.get("iterate_count", iterate_count);
if (gres.ok()) {
_iteration = std::atol(iterate_count.c_str());
// safety check
if (_iteration < 0) {
_iteration = 1;
} // if
} // if
if (description.get("result_code", value).ok()) {
_resultCode = std::atol(value.c_str());
} // if
if (description.get("preaction_result_code", value).ok()) {
std::map<std::string, std::string> pred {
{"name","TestActionBasic"}, {"result_code",value}};
if (gres.ok()) {
pred.insert({"iterate_count",iterate_count});
}
_preAction = std::make_shared<ActionDescription>(pred);
} // if
if (description.get("postaction_result_code", value).ok()) {
std::map<std::string, std::string> postd {
{"name","TestActionBasic"}, {"result_code",value}};
if (gres.ok()) {
postd.insert({"iterate_count",iterate_count});
}
_postAction = std::make_shared<ActionDescription>(postd);
} // if
};
virtual ~TestActionBasic() {};
bool first() override {
// a pre action needs to push before setting _result
if (_preDesc) {
createPreAction(_preDesc);
} else if (0==_iteration) {
// time to set result?
_result.reset(_resultCode);
} // if
// verify first() called once
if (0!=getProgress()) {
_result.reset(2);
} // if
return(iteratorEndTest());
};
bool next() override {
// time to set result?
if (0==_iteration) {
_result.reset(_resultCode);
} // if
// verify next() called properly
if (0==getProgress()) {
_result.reset(2);
} // if
return(iteratorEndTest());
};
protected:
bool iteratorEndTest() {
bool more;
//
if (_result.ok()) {
more = 0 < _iteration--;
// if going to stop, see if a postAction is needed
if (!more && _postDesc) {
createPostAction(_postDesc);
} // if
} else {
// !ok() ... always stop iteration
more = false;
}
return more;
} // iteratorEndTest
public:
int _iteration;
int _resultCode;
std::shared_ptr<ActionDescription> _preDesc;
std::shared_ptr<ActionDescription> _postDesc;
};// TestActionBasic
//
//
// Unit Tests start here
//
//
TEST_CASE("MaintenanceFeatureUnthreaded", "[cluster][maintenance][devel]") {
std::chrono::system_clock::time_point baseTime(std::chrono::system_clock::now());
std::chrono::system_clock::time_point noTime;
SECTION("Iterate Action 0 times - ok") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","0"}})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(result.ok());
REQUIRE(tf._recentAction->result().ok());
REQUIRE(0==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == COMPLETE);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(noTime == tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
}
SECTION("Iterate Action 0 times - fail") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","0"},{"result_code","1"}
})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(!result.ok());
REQUIRE(!tf._recentAction->result().ok());
REQUIRE(0==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == FAILED);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(noTime == tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
}
SECTION("Iterate Action 1 time - ok") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","1"}})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(result.ok());
REQUIRE(tf._recentAction->result().ok());
REQUIRE(1==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == COMPLETE);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(baseTime <= tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
}
SECTION("Iterate Action 1 time - fail") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","1"},{"result_code","1"}
})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(!result.ok());
REQUIRE(!tf._recentAction->result().ok());
REQUIRE(1==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == FAILED);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(baseTime <= tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
REQUIRE(tf._recentAction->getLastStatTime() <= tf._recentAction->getDoneTime());
}
SECTION("Iterate Action 2 times - ok") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","2"}
})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(result.ok());
REQUIRE(tf._recentAction->result().ok());
REQUIRE(2==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == COMPLETE);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(baseTime <= tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
REQUIRE(tf._recentAction->getLastStatTime() <= tf._recentAction->getDoneTime());
}
SECTION("Iterate Action 100 times - ok") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"}})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(result.ok());
REQUIRE(tf._recentAction->result().ok());
REQUIRE(100==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == COMPLETE);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(baseTime <= tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
REQUIRE(tf._recentAction->getLastStatTime() <= tf._recentAction->getDoneTime());
}
SECTION("Iterate Action 100 times - fail") {
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature tf(as);
tf.setSecondsActionsBlock(0); // disable retry wait for now
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"},{"result_code","1"}
})));
arangodb::Result result = tf.addAction(
std::make_shared<Action>(std::move(action_base_ptr)), true);
REQUIRE(!result.ok());
REQUIRE(!tf._recentAction->result().ok());
REQUIRE(100==tf._recentAction->getProgress());
REQUIRE(tf._recentAction->getState() == FAILED);
REQUIRE(tf._recentAction->done());
REQUIRE(1==tf._recentAction->id());
REQUIRE(baseTime <= tf._recentAction->getCreateTime());
REQUIRE(baseTime <= tf._recentAction->getStartTime());
REQUIRE(baseTime <= tf._recentAction->getDoneTime());
REQUIRE(baseTime <= tf._recentAction->getLastStatTime());
REQUIRE(tf._recentAction->getCreateTime() <= tf._recentAction->getStartTime());
REQUIRE(tf._recentAction->getStartTime() <= tf._recentAction->getDoneTime());
REQUIRE(tf._recentAction->getLastStatTime() <= tf._recentAction->getDoneTime());
}
} // MaintenanceFeatureUnthreaded
TEST_CASE("MaintenanceFeatureThreaded", "[cluster][maintenance][devel]") {
SECTION("Populate action queue and validate") {
std::vector<Expected> pre_thread, post_thread;
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature * tf = new TestMaintenanceFeature(as);
as.addFeature(tf);
std::thread th(
&arangodb::application_features::ApplicationServer::run, &as, 0, nullptr);
//
// 1. load up the queue without threads running
// a. 100 iterations then fail
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
*tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"},{"result_code","1"}
})));
arangodb::Result result = tf->addAction(
std::make_shared<Action>(std::move(action_base_ptr)), false);
REQUIRE(result.ok()); // has not executed, ok() is about parse and list add
REQUIRE(tf->_recentAction->result().ok());
pre_thread.push_back({1,0,READY,0});
post_thread.push_back({1,1,FAILED,100});
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
*tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","2"}
})));
result = tf->addAction(
std::make_shared<Action>(std::move(action_base_ptr)), false);
REQUIRE(result.ok()); // has not executed, ok() is about parse and list add
REQUIRE(tf->_recentAction->result().ok());
pre_thread.push_back({2,0,READY,0});
post_thread.push_back({2,0,COMPLETE,2});
// c. duplicate of 'a', should fail to add
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
*tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"},{"result_code","1"}
})));
result = tf->addAction(
std::make_shared<Action>(std::move(action_base_ptr)), false);
REQUIRE(!result.ok()); // has not executed, ok() is about parse and list add
// _recentAction will NOT contain the aborted object ... don't test it
//
// 2. see if happy about queue prior to threads running
REQUIRE(tf->verifyRegistryState(pre_thread));
//
// 3. start threads AFTER ApplicationServer known to be running
tf->setMaintenanceThreadsMax(1);
//
// 4. loop while waiting for threads to complete all actions
tf->waitRegistryComplete();
//
// 5. verify completed actions
REQUIRE(tf->verifyRegistryState(post_thread));
#if 0 // for debugging
std::cout << tf->toVelocyPack().toJson() << std::endl;
#endif
//
// 6. bring down the ApplicationServer, i.e. clean up
as.beginShutdown();
th.join();
}
SECTION("Action that generates a pre-action") {
std::vector<Expected> pre_thread, post_thread;
std::shared_ptr<arangodb::options::ProgramOptions> po =
std::make_shared<arangodb::options::ProgramOptions>(
"test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature * tf = new TestMaintenanceFeature(as);
as.addFeature(tf);
std::thread th(
&arangodb::application_features::ApplicationServer::run, &as, 0, nullptr);
//
// 1. load up the queue without threads running
// a. 100 iterations then fail
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
*tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"},{"preaction_result_code","0"}
})));
arangodb::Result result = tf->addAction(
std::make_shared<Action>(std::move(action_base_ptr)), false);
REQUIRE(result.ok()); // has not executed, ok() is about parse and list add
REQUIRE(tf->_recentAction->result().ok());
pre_thread.push_back({1,0,READY,0});
post_thread.push_back({1,0,COMPLETE,100});
post_thread.push_back({2,0,COMPLETE,100}); // preaction results
//
// 2. see if happy about queue prior to threads running
REQUIRE(tf->verifyRegistryState(pre_thread));
//
// 3. start threads AFTER ApplicationServer known to be running
tf->setMaintenanceThreadsMax(1);
//
// 4. loop while waiting for threads to complete all actions
tf->waitRegistryComplete();
//
// 5. verify completed actions
REQUIRE(tf->verifyRegistryState(post_thread));
#if 0 // for debugging
std::cout << tf->toVelocyPack().toJson() << std::endl;
#endif
//
// 6. bring down the ApplicationServer, i.e. clean up
as.beginShutdown();
th.join();
}
SECTION("Action that generates a post-action") {
std::vector<Expected> pre_thread, post_thread;
std::shared_ptr<arangodb::options::ProgramOptions> po = std::make_shared<arangodb::options::ProgramOptions>("test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature * tf = new TestMaintenanceFeature(as);
as.addFeature(tf);
std::thread th(&arangodb::application_features::ApplicationServer::run, &as, 0, nullptr);
//
// 1. load up the queue without threads running
// a. 100 iterations then fail
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
*tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"},{"postaction_result_code","0"}
})));
arangodb::Result result = tf->addAction(
std::make_shared<Action>(std::move(action_base_ptr)), false);
REQUIRE(result.ok()); // has not executed, ok() is about parse and list add
REQUIRE(tf->_recentAction->result().ok());
pre_thread.push_back({1,0,READY,0});
post_thread.push_back({1,0,COMPLETE,100});
post_thread.push_back({2,0,COMPLETE,100}); // postaction results
//
// 2. see if happy about queue prior to threads running
REQUIRE(tf->verifyRegistryState(pre_thread));
//
// 3. start threads AFTER ApplicationServer known to be running
tf->setMaintenanceThreadsMax(1);
//
// 4. loop while waiting for threads to complete all actions
tf->waitRegistryComplete();
//
// 5. verify completed actions
REQUIRE(tf->verifyRegistryState(post_thread));
#if 0 // for debugging
std::cout << tf->toVelocyPack().toJson() << std::endl;
#endif
//
// 6. bring down the ApplicationServer, i.e. clean up
as.beginShutdown();
th.join();
}
SECTION("Action delete") {
std::vector<Expected> pre_thread, post_thread;
std::shared_ptr<arangodb::options::ProgramOptions> po = std::make_shared<arangodb::options::ProgramOptions>("test", std::string(), std::string(), "path");
arangodb::application_features::ApplicationServer as(po, nullptr);
TestMaintenanceFeature * tf = new TestMaintenanceFeature(as);
as.addFeature(tf);
std::thread th(&arangodb::application_features::ApplicationServer::run, &as, 0, nullptr);
//
// 1. load up the queue without threads running
// a. 100 iterations then fail
std::unique_ptr<ActionBase> action_base_ptr;
action_base_ptr.reset(
(ActionBase*) new TestActionBasic(
*tf, ActionDescription(std::map<std::string,std::string>{
{"name","TestActionBasic"},{"iterate_count","100"},{"postaction_result_code","0"}
})));
arangodb::Result result = tf->addAction(
std::make_shared<Action>(std::move(action_base_ptr)), false);
REQUIRE(result.ok()); // has not executed, ok() is about parse and list add
REQUIRE(tf->_recentAction->result().ok());
pre_thread.push_back({1,0,READY,0});
post_thread.push_back({1,0,FAILED,0});
//
// 2. see if happy about queue prior to threads running
REQUIRE(tf->verifyRegistryState(pre_thread));
tf->deleteAction(1);
//
// 3. start threads AFTER ApplicationServer known to be running
tf->setMaintenanceThreadsMax(1);
//
// 4. loop while waiting for threads to complete all actions
tf->waitRegistryComplete();
//
// 5. verify completed actions
REQUIRE(tf->verifyRegistryState(post_thread));
#if 0 // for debugging
std::cout << tf->toVelocyPack().toJson() << std::endl;
#endif
//
// 6. bring down the ApplicationServer, i.e. clean up
as.beginShutdown();
th.join();
}
} // MaintenanceFeatureThreaded