Skip to content

Commit daac9e8

Browse files
authored
Fail start up if state is missing (#4)
* Fail start up if state is missing * Fail categorizer restore if state is missing
1 parent 52533e4 commit daac9e8

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
@@ -795,7 +795,7 @@ void CAnomalyJob::writeOutResults(bool interim, model::CHierarchicalResults &res
795795
typedef ml::core::CScopedRapidJsonPoolAllocator<CJsonOutputWriter> TScopedAllocator;
796796
static const std::string ALLOCATOR_ID("CAnomalyJob::writeOutResults");
797797
TScopedAllocator scopedAllocator(ALLOCATOR_ID, m_JsonOutputWriter);
798-
798+
799799
api::CHierarchicalResultsWriter writer(m_Limits, m_ModelConfig,
800800
boost::bind(&CJsonOutputWriter::acceptResult,
801801
&m_JsonOutputWriter,
@@ -888,8 +888,9 @@ bool CAnomalyJob::restoreState(core::CDataSearcher &restoreSearcher,
888888

889889
if (strm->fail())
890890
{
891-
// This is not fatal - we just didn't find the given document number
892-
return true;
891+
// This is fatal. If the stream exists and has failed then state is missing
892+
LOG_ERROR("State restoration search returned failed stream");
893+
return false;
893894
}
894895

895896
// We're dealing with streaming JSON state
@@ -960,9 +961,8 @@ bool CAnomalyJob::restoreState(core::CStateRestoreTraverser &traverser,
960961
if (traverser.isEof())
961962
{
962963
m_RestoredStateDetail.s_RestoredStateStatus = E_NoDetectorsRecovered;
963-
LOG_DEBUG("No data store results - assuming no state exists");
964-
// This is not an error if no data has been persisted
965-
return true;
964+
LOG_ERROR("Expected persisted state but no state exists");
965+
return false;
966966
}
967967

968968
core_t::TTime lastBucketEndTime(0);
@@ -996,7 +996,7 @@ bool CAnomalyJob::restoreState(core::CStateRestoreTraverser &traverser,
996996
if (stateVersion != model::CAnomalyDetector::STATE_VERSION)
997997
{
998998
m_RestoredStateDetail.s_RestoredStateStatus = E_IncorrectVersion;
999-
LOG_INFO("Restored anomaly detector state version is " << stateVersion <<
999+
LOG_ERROR("Restored anomaly detector state version is " << stateVersion <<
10001000
" - ignoring it as current state version is " <<
10011001
model::CAnomalyDetector::STATE_VERSION);
10021002

lib/api/CFieldDataTyper.cc

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

274274
if (strm->bad())
275275
{
276-
LOG_ERROR("State restoration search returned bad stream");
276+
LOG_ERROR("Categorizer state restoration returned a bad stream");
277277
return false;
278278
}
279279

280280
if (strm->fail())
281281
{
282-
// This is not fatal - we just didn't find the given document number
283-
return true;
282+
// This is fatal. If the stream exists and has failed then state is missing
283+
LOG_ERROR("Categorizer state restoration returned a failed stream");
284+
return false;
284285
}
285286

286287
// We're dealing with streaming JSON state
@@ -308,7 +309,14 @@ bool CFieldDataTyper::restoreState(core::CDataSearcher &restoreSearcher,
308309

309310
bool CFieldDataTyper::acceptRestoreTraverser(core::CStateRestoreTraverser &traverser)
310311
{
311-
if (traverser.name() == VERSION_TAG)
312+
const std::string &firstFieldName = traverser.name();
313+
if (traverser.isEof())
314+
{
315+
LOG_ERROR("Expected categorizer persisted state but no state exists");
316+
return false;
317+
}
318+
319+
if (firstFieldName == VERSION_TAG)
312320
{
313321
std::string version;
314322
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
@@ -39,6 +39,22 @@
3939
namespace
4040
{
4141

42+
//! \brief
43+
//! Mock object for state restore unit tests.
44+
//!
45+
//! DESCRIPTION:\n
46+
//! CDataSearcher that returns an empty stream.
47+
//!
48+
class CEmptySearcher : public ml::core::CDataSearcher
49+
{
50+
public:
51+
//! Do a search that results in an empty input stream.
52+
virtual TIStreamP search(size_t /*currentDocNum*/, size_t /*limit*/)
53+
{
54+
return TIStreamP(new std::istringstream());
55+
}
56+
};
57+
4258
//! \brief
4359
//! Mock object for unit tests
4460
//!
@@ -1878,6 +1894,27 @@ void CAnomalyJobTest::testInterimResultEdgeCases(void)
18781894
std::remove(logFile);
18791895
}
18801896

1897+
void CAnomalyJobTest::testRestoreFailsWithEmptyStream(void)
1898+
{
1899+
model::CLimits limits;
1900+
api::CFieldConfig fieldConfig;
1901+
api::CFieldConfig::TStrVec clauses;
1902+
clauses.push_back("value");
1903+
clauses.push_back("partitionfield=greenhouse");
1904+
fieldConfig.initFromClause(clauses);
1905+
model::CAnomalyDetectorModelConfig modelConfig =
1906+
model::CAnomalyDetectorModelConfig::defaultConfig(BUCKET_SIZE);
1907+
std::ostringstream outputStrm;
1908+
core::CJsonOutputStreamWrapper wrappedOutputStream(outputStrm);
1909+
1910+
api::CAnomalyJob job("job", limits, fieldConfig, modelConfig,
1911+
wrappedOutputStream);
1912+
1913+
core_t::TTime completeToTime(0);
1914+
CEmptySearcher restoreSearcher;
1915+
CPPUNIT_ASSERT(job.restoreState(restoreSearcher, completeToTime) == false);
1916+
}
1917+
18811918
CppUnit::Test* CAnomalyJobTest::suite(void)
18821919
{
18831920
CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite("CAnomalyJobTest");
@@ -1906,5 +1943,8 @@ CppUnit::Test* CAnomalyJobTest::suite(void)
19061943
suiteOfTests->addTest( new CppUnit::TestCaller<CAnomalyJobTest>(
19071944
"CAnomalyJobTest::testInterimResultEdgeCases",
19081945
&CAnomalyJobTest::testInterimResultEdgeCases) );
1946+
suiteOfTests->addTest( new CppUnit::TestCaller<CAnomalyJobTest>(
1947+
"CAnomalyJobTest::testRestoreFailsWithEmptyStream",
1948+
&CAnomalyJobTest::testRestoreFailsWithEmptyStream) );
19091949
return suiteOfTests;
19101950
}

lib/api/unittest/CAnomalyJobTest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class CAnomalyJobTest : public CppUnit::TestFixture
3131
void testBucketSelection(void);
3232
void testModelPlot(void);
3333
void testInterimResultEdgeCases(void);
34+
void testRestoreFailsWithEmptyStream(void);
3435

3536
static CppUnit::Test *suite(void);
3637

lib/api/unittest/CFieldDataTyperTest.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ using namespace api;
3838
namespace
3939
{
4040

41+
//! \brief
42+
//! Mock object for state restore unit tests.
43+
//!
44+
//! DESCRIPTION:\n
45+
//! CDataSearcher that returns an empty stream.
46+
//!
47+
class CEmptySearcher : public ml::core::CDataSearcher
48+
{
49+
public:
50+
//! Do a search that results in an empty input stream.
51+
virtual TIStreamP search(size_t /*currentDocNum*/, size_t /*limit*/)
52+
{
53+
return TIStreamP(new std::istringstream());
54+
}
55+
};
56+
4157
class CTestOutputHandler : public COutputHandler
4258
{
4359
public:
@@ -325,6 +341,23 @@ void CFieldDataTyperTest::testHandleControlMessages(void)
325341
output.find("[{\"flush\":{\"id\":\"7\",\"last_finalized_bucket_end\":0}}"));
326342
}
327343

344+
void CFieldDataTyperTest::testRestoreStateFailsWithEmptyState(void)
345+
{
346+
model::CLimits limits;
347+
CFieldConfig config;
348+
CPPUNIT_ASSERT(config.initFromFile("testfiles/new_persist_categorization.conf"));
349+
350+
std::ostringstream outputStrm;
351+
CNullOutput nullOutput;
352+
core::CJsonOutputStreamWrapper wrappedOutputStream(outputStrm);
353+
CJsonOutputWriter writer("job", wrappedOutputStream);
354+
CFieldDataTyper typer("job", config, limits, nullOutput, writer, nullptr);
355+
356+
core_t::TTime completeToTime(0);
357+
CEmptySearcher restoreSearcher;
358+
CPPUNIT_ASSERT(typer.restoreState(restoreSearcher, completeToTime) == false);
359+
}
360+
328361
CppUnit::Test* CFieldDataTyperTest::suite()
329362
{
330363
CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite("CFieldDataTyperTest");
@@ -341,6 +374,9 @@ CppUnit::Test* CFieldDataTyperTest::suite()
341374
suiteOfTests->addTest( new CppUnit::TestCaller<CFieldDataTyperTest>(
342375
"CFieldDataTyperTest::testHandleControlMessages",
343376
&CFieldDataTyperTest::testHandleControlMessages) );
377+
suiteOfTests->addTest( new CppUnit::TestCaller<CFieldDataTyperTest>(
378+
"CFieldDataTyperTest::testRestoreStateFailsWithEmptyState",
379+
&CFieldDataTyperTest::testRestoreStateFailsWithEmptyState) );
344380
return suiteOfTests;
345381
}
346382

lib/api/unittest/CFieldDataTyperTest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class CFieldDataTyperTest : public CppUnit::TestFixture
2626
void testNodeReverseSearch(void);
2727
void testPassOnControlMessages(void);
2828
void testHandleControlMessages(void);
29+
void testRestoreStateFailsWithEmptyState(void);
2930

3031
static CppUnit::Test *suite();
3132

lib/api/unittest/CMockSearcher.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ CMockSearcher::TIStreamP CMockSearcher::search(size_t currentDocNum, size_t /*li
2727
{
2828
if (currentDocNum == 0)
2929
{
30-
LOG_ERROR("Current doc number cannot be 0 - KV store requires 1-based numbers");
30+
LOG_ERROR("Current doc number cannot be 0 - data store requires 1-based numbers");
3131
return TIStreamP();
3232
}
3333

0 commit comments

Comments
 (0)