Skip to content

Commit 3e2f7e2

Browse files
committed
Fail start up if state is missing (#4)
* Fail start up if state is missing * Fail categorizer restore if state is missing
1 parent ef9fcba commit 3e2f7e2

File tree

7 files changed

+98
-12
lines changed

7 files changed

+98
-12
lines changed

lib/api/CAnomalyJob.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ void CAnomalyJob::writeOutResults(bool interim, model::CHierarchicalResults &res
786786
typedef ml::core::CScopedRapidJsonPoolAllocator<CJsonOutputWriter> TScopedAllocator;
787787
static const std::string ALLOCATOR_ID("CAnomalyJob::writeOutResults");
788788
TScopedAllocator scopedAllocator(ALLOCATOR_ID, m_JsonOutputWriter);
789-
789+
790790
api::CHierarchicalResultsWriter writer(m_Limits, m_ModelConfig,
791791
boost::bind(&CJsonOutputWriter::acceptResult,
792792
&m_JsonOutputWriter,
@@ -879,8 +879,9 @@ bool CAnomalyJob::restoreState(core::CDataSearcher &restoreSearcher,
879879

880880
if (strm->fail())
881881
{
882-
// This is not fatal - we just didn't find the given document number
883-
return true;
882+
// This is fatal. If the stream exists and has failed then state is missing
883+
LOG_ERROR("State restoration search returned failed stream");
884+
return false;
884885
}
885886

886887
// We're dealing with streaming JSON state
@@ -951,9 +952,8 @@ bool CAnomalyJob::restoreState(core::CStateRestoreTraverser &traverser,
951952
if (traverser.isEof())
952953
{
953954
m_RestoredStateDetail.s_RestoredStateStatus = E_NoDetectorsRecovered;
954-
LOG_DEBUG("No data store results - assuming no state exists");
955-
// This is not an error if no data has been persisted
956-
return true;
955+
LOG_ERROR("Expected persisted state but no state exists");
956+
return false;
957957
}
958958

959959
core_t::TTime lastBucketEndTime(0);
@@ -987,7 +987,7 @@ bool CAnomalyJob::restoreState(core::CStateRestoreTraverser &traverser,
987987
if (stateVersion != model::CAnomalyDetector::STATE_VERSION)
988988
{
989989
m_RestoredStateDetail.s_RestoredStateStatus = E_IncorrectVersion;
990-
LOG_INFO("Restored anomaly detector state version is " << stateVersion <<
990+
LOG_ERROR("Restored anomaly detector state version is " << stateVersion <<
991991
" - ignoring it as current state version is " <<
992992
model::CAnomalyDetector::STATE_VERSION);
993993

lib/api/CFieldDataTyper.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,14 +264,15 @@ bool CFieldDataTyper::restoreState(core::CDataSearcher &restoreSearcher,
264264

265265
if (strm->bad())
266266
{
267-
LOG_ERROR("State restoration search returned bad stream");
267+
LOG_ERROR("Categorizer state restoration returned a bad stream");
268268
return false;
269269
}
270270

271271
if (strm->fail())
272272
{
273-
// This is not fatal - we just didn't find the given document number
274-
return true;
273+
// This is fatal. If the stream exists and has failed then state is missing
274+
LOG_ERROR("Categorizer state restoration returned a failed stream");
275+
return false;
275276
}
276277

277278
// We're dealing with streaming JSON state
@@ -299,7 +300,14 @@ bool CFieldDataTyper::restoreState(core::CDataSearcher &restoreSearcher,
299300

300301
bool CFieldDataTyper::acceptRestoreTraverser(core::CStateRestoreTraverser &traverser)
301302
{
302-
if (traverser.name() == VERSION_TAG)
303+
const std::string &firstFieldName = traverser.name();
304+
if (traverser.isEof())
305+
{
306+
LOG_ERROR("Expected categorizer persisted state but no state exists");
307+
return false;
308+
}
309+
310+
if (firstFieldName == VERSION_TAG)
303311
{
304312
std::string version;
305313
if (core::CStringUtils::stringToType(traverser.value(), version) == false)

lib/api/unittest/CAnomalyJobTest.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@
3030
namespace
3131
{
3232

33+
//! \brief
34+
//! Mock object for state restore unit tests.
35+
//!
36+
//! DESCRIPTION:\n
37+
//! CDataSearcher that returns an empty stream.
38+
//!
39+
class CEmptySearcher : public ml::core::CDataSearcher
40+
{
41+
public:
42+
//! Do a search that results in an empty input stream.
43+
virtual TIStreamP search(size_t /*currentDocNum*/, size_t /*limit*/)
44+
{
45+
return TIStreamP(new std::istringstream());
46+
}
47+
};
48+
3349
//! \brief
3450
//! Mock object for unit tests
3551
//!
@@ -1869,6 +1885,27 @@ void CAnomalyJobTest::testInterimResultEdgeCases(void)
18691885
std::remove(logFile);
18701886
}
18711887

1888+
void CAnomalyJobTest::testRestoreFailsWithEmptyStream(void)
1889+
{
1890+
model::CLimits limits;
1891+
api::CFieldConfig fieldConfig;
1892+
api::CFieldConfig::TStrVec clauses;
1893+
clauses.push_back("value");
1894+
clauses.push_back("partitionfield=greenhouse");
1895+
fieldConfig.initFromClause(clauses);
1896+
model::CAnomalyDetectorModelConfig modelConfig =
1897+
model::CAnomalyDetectorModelConfig::defaultConfig(BUCKET_SIZE);
1898+
std::ostringstream outputStrm;
1899+
core::CJsonOutputStreamWrapper wrappedOutputStream(outputStrm);
1900+
1901+
api::CAnomalyJob job("job", limits, fieldConfig, modelConfig,
1902+
wrappedOutputStream);
1903+
1904+
core_t::TTime completeToTime(0);
1905+
CEmptySearcher restoreSearcher;
1906+
CPPUNIT_ASSERT(job.restoreState(restoreSearcher, completeToTime) == false);
1907+
}
1908+
18721909
CppUnit::Test* CAnomalyJobTest::suite(void)
18731910
{
18741911
CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite("CAnomalyJobTest");
@@ -1897,5 +1934,8 @@ CppUnit::Test* CAnomalyJobTest::suite(void)
18971934
suiteOfTests->addTest( new CppUnit::TestCaller<CAnomalyJobTest>(
18981935
"CAnomalyJobTest::testInterimResultEdgeCases",
18991936
&CAnomalyJobTest::testInterimResultEdgeCases) );
1937+
suiteOfTests->addTest( new CppUnit::TestCaller<CAnomalyJobTest>(
1938+
"CAnomalyJobTest::testRestoreFailsWithEmptyStream",
1939+
&CAnomalyJobTest::testRestoreFailsWithEmptyStream) );
19001940
return suiteOfTests;
19011941
}

lib/api/unittest/CAnomalyJobTest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class CAnomalyJobTest : public CppUnit::TestFixture
2222
void testBucketSelection(void);
2323
void testModelPlot(void);
2424
void testInterimResultEdgeCases(void);
25+
void testRestoreFailsWithEmptyStream(void);
2526

2627
static CppUnit::Test *suite(void);
2728

lib/api/unittest/CFieldDataTyperTest.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ using namespace api;
2929
namespace
3030
{
3131

32+
//! \brief
33+
//! Mock object for state restore unit tests.
34+
//!
35+
//! DESCRIPTION:\n
36+
//! CDataSearcher that returns an empty stream.
37+
//!
38+
class CEmptySearcher : public ml::core::CDataSearcher
39+
{
40+
public:
41+
//! Do a search that results in an empty input stream.
42+
virtual TIStreamP search(size_t /*currentDocNum*/, size_t /*limit*/)
43+
{
44+
return TIStreamP(new std::istringstream());
45+
}
46+
};
47+
3248
class CTestOutputHandler : public COutputHandler
3349
{
3450
public:
@@ -316,6 +332,23 @@ void CFieldDataTyperTest::testHandleControlMessages(void)
316332
output.find("[{\"flush\":{\"id\":\"7\",\"last_finalized_bucket_end\":0}}"));
317333
}
318334

335+
void CFieldDataTyperTest::testRestoreStateFailsWithEmptyState(void)
336+
{
337+
model::CLimits limits;
338+
CFieldConfig config;
339+
CPPUNIT_ASSERT(config.initFromFile("testfiles/new_persist_categorization.conf"));
340+
341+
std::ostringstream outputStrm;
342+
CNullOutput nullOutput;
343+
core::CJsonOutputStreamWrapper wrappedOutputStream(outputStrm);
344+
CJsonOutputWriter writer("job", wrappedOutputStream);
345+
CFieldDataTyper typer("job", config, limits, nullOutput, writer, nullptr);
346+
347+
core_t::TTime completeToTime(0);
348+
CEmptySearcher restoreSearcher;
349+
CPPUNIT_ASSERT(typer.restoreState(restoreSearcher, completeToTime) == false);
350+
}
351+
319352
CppUnit::Test* CFieldDataTyperTest::suite()
320353
{
321354
CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite("CFieldDataTyperTest");
@@ -332,6 +365,9 @@ CppUnit::Test* CFieldDataTyperTest::suite()
332365
suiteOfTests->addTest( new CppUnit::TestCaller<CFieldDataTyperTest>(
333366
"CFieldDataTyperTest::testHandleControlMessages",
334367
&CFieldDataTyperTest::testHandleControlMessages) );
368+
suiteOfTests->addTest( new CppUnit::TestCaller<CFieldDataTyperTest>(
369+
"CFieldDataTyperTest::testRestoreStateFailsWithEmptyState",
370+
&CFieldDataTyperTest::testRestoreStateFailsWithEmptyState) );
335371
return suiteOfTests;
336372
}
337373

lib/api/unittest/CFieldDataTyperTest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class CFieldDataTyperTest : public CppUnit::TestFixture
1717
void testNodeReverseSearch(void);
1818
void testPassOnControlMessages(void);
1919
void testHandleControlMessages(void);
20+
void testRestoreStateFailsWithEmptyState(void);
2021

2122
static CppUnit::Test *suite();
2223

lib/api/unittest/CMockSearcher.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ CMockSearcher::TIStreamP CMockSearcher::search(size_t currentDocNum, size_t /*li
1818
{
1919
if (currentDocNum == 0)
2020
{
21-
LOG_ERROR("Current doc number cannot be 0 - KV store requires 1-based numbers");
21+
LOG_ERROR("Current doc number cannot be 0 - data store requires 1-based numbers");
2222
return TIStreamP();
2323
}
2424

0 commit comments

Comments
 (0)