From 90867da664858e624c5a228bae3b430fc71bd474 Mon Sep 17 00:00:00 2001 From: Tapan Karwa Date: Fri, 9 Dec 2016 14:31:13 -0800 Subject: [PATCH] Code for control-node retrieving config via cassandra. Add code to read one row from cassandra and process the read columns. Also, add code to convert the incoming column data to a json document that will finally be fed to the json parser. The conversion is done by the ConfigCass2JsonAdapter class and handles the quirks in the data stored in cassandra. Also, add code to read all-rows on startup (bulk sync). Change-Id: I0ea706554024e314772147fb35c722d008c05fd8 Partial-Bug: #1632470 --- src/ifmap/client/SConscript | 1 + src/ifmap/client/config_cass2json_adapter.cc | 62 ++++++++++ src/ifmap/client/config_cass2json_adapter.h | 32 +++++ src/ifmap/client/config_cassandra_client.cc | 122 ++++++++++++++++++- src/ifmap/client/config_cassandra_client.h | 21 +++- src/ifmap/client/config_client_manager.cc | 13 +- src/ifmap/client/config_client_manager.h | 3 + src/ifmap/client/config_json_parser.cc | 14 --- src/ifmap/client/config_json_parser.h | 6 +- src/ifmap/client/json_adapter_data.h | 21 ++++ src/ifmap/client/test/SConscript | 22 +++- src/ifmap/ifmap_log.sandesh | 16 +++ 12 files changed, 299 insertions(+), 34 deletions(-) create mode 100644 src/ifmap/client/config_cass2json_adapter.cc create mode 100644 src/ifmap/client/config_cass2json_adapter.h create mode 100644 src/ifmap/client/json_adapter_data.h diff --git a/src/ifmap/client/SConscript b/src/ifmap/client/SConscript index 8ca6bacbb4a..54265e8940a 100644 --- a/src/ifmap/client/SConscript +++ b/src/ifmap/client/SConscript @@ -21,6 +21,7 @@ libifmapio = env.Library('ifmapio', 'ifmap_state_machine.cc', 'ifmap_channel.cc', 'peer_server_finder.cc', + 'config_cass2json_adapter.cc', 'config_cassandra_client.cc', 'config_client_manager.cc', 'config_db_client.cc', diff --git a/src/ifmap/client/config_cass2json_adapter.cc b/src/ifmap/client/config_cass2json_adapter.cc new file mode 100644 index 00000000000..9bec031ed36 --- /dev/null +++ b/src/ifmap/client/config_cass2json_adapter.cc @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 Juniper Networks, Inc. All rights reserved. + */ + +#include "config_cass2json_adapter.h" + +#include + +using namespace std; + +const string ConfigCass2JsonAdapter::prop_prefix = "prop:"; +const string ConfigCass2JsonAdapter::meta_prefix = "META:"; +const string ConfigCass2JsonAdapter::comma_str = ","; + +ConfigCass2JsonAdapter::ConfigCass2JsonAdapter(const CassColumnKVVec &cdvec) + : prop_plen_(prop_prefix.size()), + meta_plen_(meta_prefix.size()) { + CreateJsonString(cdvec); +} + +// Return true if the caller needs to append a comma. False otherwise. +bool ConfigCass2JsonAdapter::AddOneEntry(const CassColumnKVVec &cdvec, int i) { + // If the key has 'prop:' at the start, remove it. + if (cdvec.at(i).key.substr(0, prop_plen_) == prop_prefix) { + doc_string_ += string( + "\"" + cdvec.at(i).key.substr(prop_plen_) + + "\"" + ": " + cdvec.at(i).value); + } else if (cdvec.at(i).key.substr(0, meta_plen_) == meta_prefix) { + // If the key has 'META:' at the start, ignore the column. + return false; + } else if (cdvec.at(i).key.compare("type") == 0) { + // Prepend the 'type'. This is "our key", with value being the json + // sub-document containing all other columns. + doc_string_ = string("{\n" + cdvec.at(i).value + ":" + "{\n") + + doc_string_; + return false; + } else { + doc_string_ += string("\"" + cdvec.at(i).key + "\"" + ": " + + cdvec.at(i).value); + } + return true; +} + +bool ConfigCass2JsonAdapter::CreateJsonString(const CassColumnKVVec &cdvec) { + for (size_t i = 0; i < cdvec.size(); ++i) { + if (AddOneEntry(cdvec, i)) { + doc_string_ += comma_str; + } + } + + // Remove the comma after the last entry. + if (doc_string_[doc_string_.size() - 1] == ',') { + doc_string_.erase(doc_string_.size() - 1); + } + + // Add one brace to close out the type's value and one to close out the + // whole json document. + doc_string_ += string("\n}\n}"); + + return true; +} + diff --git a/src/ifmap/client/config_cass2json_adapter.h b/src/ifmap/client/config_cass2json_adapter.h new file mode 100644 index 00000000000..2dd0f0946d0 --- /dev/null +++ b/src/ifmap/client/config_cass2json_adapter.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 Juniper Networks, Inc. All rights reserved. + */ + +#ifndef ctrlplane_config_cass2json_adapter_h +#define ctrlplane_config_cass2json_adapter_h + +#include "json_adapter_data.h" + +// The purpose of this class is to convert key-value pairs received from +// cassandra into one single json string. +// The user will pass a vector of key-value while creating the object. The +// constructor will create a json string, which will then be accessible via the +// doc_string() accessor. +class ConfigCass2JsonAdapter { +public: + ConfigCass2JsonAdapter(const CassColumnKVVec &cdvec); + const std::string &doc_string() { return doc_string_; } + +private: + static const std::string prop_prefix; + static const std::string meta_prefix; + static const std::string comma_str; + bool CreateJsonString(const CassColumnKVVec &cdvec); + bool AddOneEntry(const CassColumnKVVec &cdvec, int i); + + std::string doc_string_; + int prop_plen_; + int meta_plen_; +}; + +#endif // ctrlplane_config_cass2json_adapter_h diff --git a/src/ifmap/client/config_cassandra_client.cc b/src/ifmap/client/config_cassandra_client.cc index a1112acab71..664feefdb14 100644 --- a/src/ifmap/client/config_cassandra_client.cc +++ b/src/ifmap/client/config_cassandra_client.cc @@ -5,6 +5,7 @@ #include "config_cassandra_client.h" #include "base/logging.h" +#include "config_cass2json_adapter.h" #include "io/event_manager.h" #include "database/cassandra/cql/cql_if.h" #include "ifmap/ifmap_log.h" @@ -12,13 +13,19 @@ #include "sandesh/common/vns_constants.h" -const std::string ConfigCassandraClient::kUuidTableName = "obj_uuid_table"; -const std::string ConfigCassandraClient::kFqnTableName = "obj_fq_name_table"; -const std::string ConfigCassandraClient::kCassClientTaskId = "CN:CassClient"; +#include +#include + +using namespace std; + +const string ConfigCassandraClient::kUuidTableName = "obj_uuid_table"; +const string ConfigCassandraClient::kFqnTableName = "obj_fq_name_table"; +const string ConfigCassandraClient::kCassClientTaskId = "CN:CassClient"; ConfigCassandraClient::ConfigCassandraClient(EventManager *evm, - const IFMapConfigOptions &options) - : ConfigDbClient(options), evm_(evm) { + const IFMapConfigOptions &options, + ConfigJsonParser *in_parser) + : ConfigDbClient(options), evm_(evm), parser_(in_parser) { dbif_.reset(new cass::cql::CqlIf(evm, config_db_ips(), GetFirstConfigDbPort(), "", "")); } @@ -34,6 +41,54 @@ void ConfigCassandraClient::InitRetry() { sleep(kInitRetryTimeSec); } +bool ConfigCassandraClient::ParseUuidTableRowResponse(const string &uuid, + const GenDb::ColList &col_list, CassColumnKVVec *cass_data_vec) { + + string uuid_as_str(string("\"" + uuid + "\"")); + cass_data_vec->push_back(JsonAdapterDataType("uuid", uuid_as_str)); + BOOST_FOREACH(const GenDb::NewCol &ncol, col_list.columns_) { + assert(ncol.name->size() == 1); + assert(ncol.value->size() == 1); + + const GenDb::DbDataValue &dname(ncol.name->at(0)); + assert(dname.which() == GenDb::DB_VALUE_BLOB); + GenDb::Blob dname_blob(boost::get(dname)); + string key(reinterpret_cast(dname_blob.data()), + dname_blob.size()); + std::cout << "key is " << key; + + const GenDb::DbDataValue &dvalue(ncol.value->at(0)); + assert(dvalue.which() == GenDb::DB_VALUE_STRING); + string value(boost::get(dvalue)); + std::cout << " and value is " << value << std::endl; + + cass_data_vec->push_back(JsonAdapterDataType(key, value)); + } + + cout << "Filled in " << cass_data_vec->size() << " entries\n"; + return true; +} + +bool ConfigCassandraClient::ParseRowAndEnqueueToParser(const string &uuid_key, + const GenDb::ColList &col_list) { + auto_ptr cass_data_vec(new CassColumnKVVec()); + if (ParseUuidTableRowResponse(uuid_key, col_list, cass_data_vec.get())) { + // Convert column data to json string. + ConfigCass2JsonAdapter *ccja = + new ConfigCass2JsonAdapter(*(cass_data_vec.get())); + cout << "doc-string is\n" << ccja->doc_string() << endl; + + // Enqueue ccja to the parser here. + // parser_->Receive(ccja->doc_string().....); + } else { + IFMAP_WARN(IFMapGetRowError, "Parsing row response failed for table", + kUuidTableName); + return false; + } + + return true; +} + void ConfigCassandraClient::InitDatabase() { while (true) { if (!dbif_->Db_Init()) { @@ -58,5 +113,62 @@ void ConfigCassandraClient::InitDatabase() { } break; } + // TODO: remove this after all testing. + //ReadUuidTableRow("e6e5609b-64f8-4238-82e6-163e2ec11d21"); + //ReadUuidTableRow("e6e5609b-64f8-4238-82e6-abce2ec1wxyz"); + BulkDataSync(); +} + +bool ConfigCassandraClient::ReadUuidTableRow(const string &uuid_key) { + GenDb::ColList col_list; + GenDb::DbDataValueVec key; + + key.push_back(GenDb::Blob( + reinterpret_cast(uuid_key.c_str()), uuid_key.size())); + + if (dbif_->Db_GetRow(&col_list, kUuidTableName, key, + GenDb::DbConsistency::QUORUM)) { + if (col_list.columns_.size()) { + ParseRowAndEnqueueToParser(uuid_key, col_list); + } + } else { + IFMAP_WARN(IFMapGetRowError, "GetRow failed for table", kUuidTableName); + return false; + } + + return true; +} + +bool ConfigCassandraClient::ReadAllUuidTableRows() { + GenDb::ColListVec cl_vec; + + if (dbif_->Db_GetAllRows(&cl_vec, kUuidTableName, + GenDb::DbConsistency::QUORUM)) { + cout << "All Rows size " << cl_vec.size() << endl; + int count = 0; + BOOST_FOREACH(const GenDb::ColList &cl_list, cl_vec) { + assert(cl_list.rowkey_.size() == 1); + assert(cl_list.rowkey_[0].which() == GenDb::DB_VALUE_BLOB); + GenDb::Blob brk(boost::get(cl_list.rowkey_[0])); + string uuid_key(reinterpret_cast(brk.data()), + brk.size()); + cout << "Row " << ++count << " num-cols " + << cl_list.columns_.size() << " and key " << uuid_key << endl; + if (cl_list.columns_.size()) { + ParseRowAndEnqueueToParser(uuid_key, cl_list); + } + } + } else { + IFMAP_WARN(IFMapGetRowError, "GetAllRows failed for table", + kUuidTableName); + return false; + } + + return true; +} + +bool ConfigCassandraClient::BulkDataSync() { + ReadAllUuidTableRows(); + return true; } diff --git a/src/ifmap/client/config_cassandra_client.h b/src/ifmap/client/config_cassandra_client.h index af35d17d19b..aa6cc778749 100644 --- a/src/ifmap/client/config_cassandra_client.h +++ b/src/ifmap/client/config_cassandra_client.h @@ -2,13 +2,16 @@ * Copyright (c) 2016 Juniper Networks, Inc. All rights reserved. */ -#ifndef ctrlplane_cass_config_client_h -#define ctrlplane_cass_config_client_h +#ifndef ctrlplane_config_cass_client_h +#define ctrlplane_config_cass_client_h #include "config_db_client.h" +#include "config_json_parser.h" #include "database/gendb_if.h" +#include "json_adapter_data.h" class EventManager; +class ConfigJsonParser; /* * This class has the functionality to interact with the cassandra servers that @@ -24,15 +27,25 @@ class ConfigCassandraClient : public ConfigDbClient { typedef boost::scoped_ptr GenDbIfPtr; - ConfigCassandraClient(EventManager *evm, const IFMapConfigOptions &options); + ConfigCassandraClient(EventManager *evm, const IFMapConfigOptions &options, + ConfigJsonParser *in_parser); virtual ~ConfigCassandraClient(); virtual void InitDatabase(); + bool ReadUuidTableRow(const std::string &uuid); private: void InitRetry(); + bool ParseUuidTableRowResponse(const std::string &uuid, + const GenDb::ColList &col_list, CassColumnKVVec *cass_data_vec); + void AddUuidEntry(const string &uuid); + bool BulkDataSync(); + bool ReadAllUuidTableRows(); + bool ParseRowAndEnqueueToParser(const string &uuid_key, + const GenDb::ColList &col_list); EventManager *evm_; GenDbIfPtr dbif_; + ConfigJsonParser *parser_; }; -#endif // ctrlplane_cass_config_client_h +#endif // ctrlplane_config_cass_client_h diff --git a/src/ifmap/client/config_client_manager.cc b/src/ifmap/client/config_client_manager.cc index 35121d8eb38..8d272e3cd9c 100644 --- a/src/ifmap/client/config_client_manager.cc +++ b/src/ifmap/client/config_client_manager.cc @@ -7,21 +7,28 @@ #include "base/task.h" #include "ifmap/ifmap_config_options.h" +#include "ifmap/ifmap_server.h" #include "io/event_manager.h" int ConfigClientManager::thread_count_; ConfigClientManager::ConfigClientManager(EventManager *evm, IFMapServer *ifmap_server, const IFMapConfigOptions& config_options) - : evm_(evm), ifmap_server_(ifmap_server) { - config_db_client_.reset(new ConfigCassandraClient(evm, config_options)); - thread_count_ = TaskScheduler::GetInstance()->HardwareThreadCount(); + : evm_(evm), ifmap_server_(ifmap_server) { + config_json_parser_.reset(new ConfigJsonParser(ifmap_server_->database())); + config_db_client_.reset(new ConfigCassandraClient(evm, config_options, + config_json_parser_.get())); + thread_count_ = TaskScheduler::GetInstance()->HardwareThreadCount(); } void ConfigClientManager::Initialize() { config_db_client_->InitDatabase(); } +ConfigJsonParser *ConfigClientManager::config_json_parser() const { + return config_json_parser_.get(); +} + ConfigDbClient *ConfigClientManager::config_db_client() const { return config_db_client_.get(); } diff --git a/src/ifmap/client/config_client_manager.h b/src/ifmap/client/config_client_manager.h index 6a3a4ef9775..c0993e28300 100644 --- a/src/ifmap/client/config_client_manager.h +++ b/src/ifmap/client/config_client_manager.h @@ -8,6 +8,7 @@ #include class ConfigDbClient; +class ConfigJsonParser; class EventManager; class IFMapServer; struct IFMapConfigOptions; @@ -24,6 +25,7 @@ class ConfigClientManager { const IFMapConfigOptions& config_options); void Initialize(); ConfigDbClient *config_db_client() const; + ConfigJsonParser *config_json_parser() const; bool GetEndOfRibComputed() const; private: @@ -31,6 +33,7 @@ class ConfigClientManager { EventManager *evm_; IFMapServer *ifmap_server_; + boost::scoped_ptr config_json_parser_; boost::scoped_ptr config_db_client_; }; diff --git a/src/ifmap/client/config_json_parser.cc b/src/ifmap/client/config_json_parser.cc index 638f9911241..fad34d93b04 100644 --- a/src/ifmap/client/config_json_parser.cc +++ b/src/ifmap/client/config_json_parser.cc @@ -11,7 +11,6 @@ using namespace rapidjson; using namespace std; ConfigJsonParser::ConfigJsonParser(DB *db) : db_(db) { - } void ConfigJsonParser::MetadataRegister(const string &metadata, @@ -248,20 +247,7 @@ bool ConfigJsonParser::Receive(const string &in_message, bool add_change, } else { cout << "No parse error\n"; ParseDocument(document, add_change, origin, &req_list); - //TmpParseDocument(document); } return true; } -// For testing purposes only. Delete before release. -void ConfigJsonParser::TmpParseDocument(const rapidjson::Document &document) { - for (Value::ConstMemberIterator itr = document.MemberBegin(); - itr != document.MemberEnd(); ++itr) { - cout << "Key:" << itr->name.GetString(); - if (itr->value.IsNull()) cout << endl; - else { - cout << "Value" << endl; - } - } -} - diff --git a/src/ifmap/client/config_json_parser.h b/src/ifmap/client/config_json_parser.h index cfa3a892a1a..7caf3e3d98e 100644 --- a/src/ifmap/client/config_json_parser.h +++ b/src/ifmap/client/config_json_parser.h @@ -9,12 +9,14 @@ #include #include +#include "base/queue_task.h" #include "ifmap/ifmap_table.h" #include "ifmap/ifmap_origin.h" -#include #include "rapidjson/document.h" +#include + struct AutogenProperty; class DB; struct DBRequest; @@ -34,8 +36,6 @@ class ConfigJsonParser { bool Receive(const std::string &in_message, bool add_change, IFMapOrigin::Origin origin); - void TmpParseDocument(const rapidjson::Document &document); - private: bool ParseDocument(const rapidjson::Document &document, bool add_change, IFMapOrigin::Origin origin, RequestList *req_list) const; diff --git a/src/ifmap/client/json_adapter_data.h b/src/ifmap/client/json_adapter_data.h new file mode 100644 index 00000000000..c9e967fea82 --- /dev/null +++ b/src/ifmap/client/json_adapter_data.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016 Juniper Networks, Inc. All rights reserved. + */ + +#ifndef ctrlplane_json_adapter_data_h +#define ctrlplane_json_adapter_data_h + +#include +#include + +struct JsonAdapterDataType { + JsonAdapterDataType(const std::string &k, const std::string &v) + : key(k), value(v) { + } + std::string key; + std::string value; +}; + +typedef std::vector CassColumnKVVec; + +#endif // ctrlplane_json_adapter_data_h diff --git a/src/ifmap/client/test/SConscript b/src/ifmap/client/test/SConscript index 9e66d269edb..f21b7414698 100644 --- a/src/ifmap/client/test/SConscript +++ b/src/ifmap/client/test/SConscript @@ -16,12 +16,18 @@ env.Append(LIBPATH = env['TOP'] + '/db') env.Append(LIBPATH = env['TOP'] + '/base') env.Append(LIBPATH = env['TOP'] + '/bgp') env.Append(LIBPATH = env['TOP'] + '/base/test') +env.Append(LIBPATH = env['TOP'] + '/control-node') env.Append(LIBPATH = env['TOP'] + '/ifmap') +env.Append(LIBPATH = env['TOP'] + '/ifmap/test') env.Append(LIBPATH = env['TOP'] + '/xml') env.Append(LIBPATH = env['TOP'] + '/xmpp') env.Append(LIBPATH = env['TOP'] + '/schema') env.Append(LIBPATH = env['TOP'] + '/discovery/client') +env.Library('ifmap_test_util', ['ifmap_test_util.cc']) +env.Library('ifmap_test_util_server', ['ifmap_test_util_server.cc']) +env.Prepend(LIBS = ['ifmap_test_util', 'ifmap_test_util_server']) + libboostssltest = env.Library('boostssl', [ 'boost_ssl_client.cc', @@ -31,11 +37,12 @@ libboostssltest = env.Library('boostssl', env.Install(env['TOP_LIB'], libboostssltest) env.Prepend(LIBS = [ - 'boostssl', - 'ifmapio', 'ifmap_server', 'ds', 'xmpp', 'peer_sandesh', + 'boostssl', 'ifmapio', 'bgp_schema', 'ifmap_vnc', 'ifmap_server', + 'ds', 'xmpp', 'peer_sandesh', 'sandesh', 'http', 'http_parser', 'httpc', - 'curl', 'sandeshvns', 'process_info', 'io', 'ifmap_common', - 'ifmap_vnc', 'pugixml', 'xml', 'task_test', 'db', 'curl', + 'curl', 'sandeshvns', 'process_info', 'io', 'control_node', + 'ifmap_common', 'bgp_schema', 'ifmap_vnc', + 'pugixml', 'xml', 'task_test', 'db', 'curl', 'base', 'gunit', 'crypto', 'ssl', 'boost_regex' ]) @@ -54,8 +61,13 @@ peer_server_finder_test = env.UnitTest('peer_server_finder_test', ['peer_server_finder_test.cc']) env.Alias('src/ifmap/client:peer_server_finder_test', peer_server_finder_test) +#config_json_parser_test = env.UnitTest('config_json_parser_test', +# ['config_json_parser_test.cc']) +#env.Alias('src/ifmap/client:config_json_parser_test', config_json_parser_test) + client_unit_tests = [ - peer_server_finder_test + peer_server_finder_test, + #config_json_parser_test, ] client_test = env.TestSuite('ifmap-test', client_unit_tests) diff --git a/src/ifmap/ifmap_log.sandesh b/src/ifmap/ifmap_log.sandesh index 15cabdb5cf2..7b27d797d96 100644 --- a/src/ifmap/ifmap_log.sandesh +++ b/src/ifmap/ifmap_log.sandesh @@ -430,6 +430,17 @@ systemlog sandesh IFMapJsonLoadError { 4: string parse_error } +/** + * @description: System log for IFMap module + * @severity: ERROR + * @cause: Error detected while reading a row from the database + * @action: Contact your technical support representative + */ +systemlog sandesh IFMapGetRowError { + 1: string message + 2: string table_name +} + /** * @description: Trace message for IFMap module * @severity: DEBUG @@ -769,6 +780,11 @@ trace sandesh IFMapJsonLoadErrorTrace { 4: string parse_error } +trace sandesh IFMapGetRowErrorTrace { + 1: string message + 2: string table_name +} + trace sandesh IFMapUSSplitBlocked { 1: string message1 2: string blocked_set