LCOV - code coverage report
Current view: top level - root/contrail/src/contrail-common/config-client-mgr - config_amqp_client.cc (source / functions) Hit Total Coverage
Test: OpenSDN C/C++ coverage (all TARGET_SET jobs) Lines: 184 253 72.7 %
Date: 2026-08-03 02:19:58 Functions: 12 14 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 2017 Juniper Networks, Inc. All rights reserved.
       3             :  */
       4             : 
       5             : #include "config_amqp_client.h"
       6             : 
       7             : #include <boost/algorithm/string/find.hpp>
       8             : #include <boost/lexical_cast.hpp>
       9             : #include <stdio.h>
      10             : #include <string>
      11             : 
      12             : #include <SimpleAmqpClient/SimpleAmqpClient.h>
      13             : #include "rapidjson/document.h"
      14             : 
      15             : #include "base/connection_info.h"
      16             : #include "base/task.h"
      17             : #include "base/address_util.h"
      18             : #include "base/string_util.h"
      19             : #include "config_factory.h"
      20             : #include "config_cassandra_client.h"
      21             : #include "config_client_log.h"
      22             : #include "config_client_log_types.h"
      23             : #include "config_client_manager.h"
      24             : #include "config_db_client.h"
      25             : #include "config_client_show_types.h"
      26             : 
      27             : using namespace boost;
      28             : using namespace std;
      29             : using namespace contrail_rapidjson;
      30             : 
      31             : bool ConfigAmqpClient::disable_;
      32             : 
      33             : class ConfigAmqpClient::RabbitMQReader : public Task {
      34             : public:
      35           2 :     RabbitMQReader(ConfigAmqpClient *amqpclient) :
      36           2 :             Task(amqpclient->reader_task_id()), amqpclient_(amqpclient) {
      37           2 :         channel_.reset(ConfigStaticObjectFactory::Create<ConfigAmqpChannel>());
      38             : 
      39             :         // Connect to rabbit-mq asap so that notification messages over
      40             :         // rabbit mq are never missed (during bulk db sync which happens
      41             :         // soon afterwards.
      42           2 :         ConnectToRabbitMQ();
      43           2 :     }
      44             : 
      45             :     virtual bool Run();
      46           0 :     string Description() const { return "ConfigAmqpClient::RabbitMQReader"; }
      47             : 
      48             : private:
      49             :     ConfigAmqpClient *amqpclient_;
      50             :     boost::scoped_ptr<ConfigAmqpChannel> channel_;
      51             :     string consumer_tag_;
      52             :     void ConnectToRabbitMQ(bool queue_delete = true);
      53             :     bool AckRabbitMessages(AmqpClient::Envelope::ptr_t &envelop);
      54             :     bool ReceiveRabbitMessages(AmqpClient::Envelope::ptr_t &envelop);
      55             : };
      56             : 
      57        3673 : ConfigAmqpClient::ConfigAmqpClient(ConfigClientManager *mgr, string hostname,
      58        3673 :                       string module_name, const ConfigClientOptions &options) :
      59        3673 :     mgr_(mgr), hostname_(hostname), module_name_(module_name),
      60        3673 :     current_server_index_(0), terminate_(false),
      61        3673 :     rabbitmq_user_(options.rabbitmq_user),
      62        3673 :     rabbitmq_password_(options.rabbitmq_password),
      63        3673 :     rabbitmq_vhost_(options.rabbitmq_vhost),
      64        3673 :     rabbitmq_use_ssl_(options.rabbitmq_use_ssl),
      65        3673 :     rabbitmq_ssl_version_(options.rabbitmq_ssl_version),
      66        3673 :     rabbitmq_ssl_keyfile_(options.rabbitmq_ssl_keyfile),
      67        3673 :     rabbitmq_ssl_certfile_(options.rabbitmq_ssl_certfile),
      68       11019 :     rabbitmq_ssl_ca_certs_(options.rabbitmq_ssl_ca_certs) {
      69             : 
      70        3673 :     connection_status_ = false;
      71        3673 :     connection_status_change_at_ = UTCTimestampUsec();
      72             : 
      73        3673 :     TaskScheduler *scheduler = TaskScheduler::GetInstance();
      74        3673 :     reader_task_id_ = scheduler->GetTaskId("amqp::RabbitMQReader");
      75             : 
      76        3673 :     if (options.rabbitmq_server_list.empty())
      77        3671 :         return;
      78             : 
      79           2 :     for (vector<string>::const_iterator iter =
      80           2 :                 options.rabbitmq_server_list.begin();
      81           4 :          iter != options.rabbitmq_server_list.end(); iter++) {
      82           2 :         string server_info(*iter);
      83             :         typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
      84           2 :         boost::char_separator<char> sep(":");
      85           2 :         tokenizer tokens(server_info, sep);
      86           2 :         tokenizer::iterator tit = tokens.begin();
      87           2 :         string ip(*tit);
      88           2 :         rabbitmq_ips_.push_back(ip);
      89           2 :         ++tit;
      90           2 :         string port_str(*tit);
      91           2 :         rabbitmq_ports_.push_back(port_str);
      92           2 :         Endpoint curr_ep;
      93           2 :         int port = 0;
      94           2 :         stringToInteger(port_str, port);
      95           2 :         boost::system::error_code ec;
      96           2 :         curr_ep.address(AddressFromString(ip, &ec));
      97           2 :         curr_ep.port(port);
      98           2 :         endpoints_.push_back(curr_ep);
      99           2 :     }
     100           0 : }
     101             : 
     102        7740 : void ConfigAmqpClient::StartRabbitMQReader() {
     103        7740 :     if (disable_)  {
     104        7738 :         CONFIG_CLIENT_DEBUG(
     105             :             ConfigClientMgrDebug,
     106             :             "RabbitMQ SM: StartRabbitMQReader: RabbitMQ disabled");
     107        7738 :         return;
     108             :     }
     109             : 
     110             :     // If reinit is triggerred, Don't start the rabbitmq reader
     111           2 :     if (config_manager()->is_reinit_triggered()) {
     112           0 :         CONFIG_CLIENT_DEBUG(
     113             :             ConfigClientMgrDebug,
     114             :             "RabbitMQ SM: StartRabbitMQReader: re init triggered,"
     115             :             " dont start RabbitMQ");
     116           0 :         return;
     117             :     }
     118           2 :     TaskScheduler *scheduler = TaskScheduler::GetInstance();
     119           2 :     Task *task = new RabbitMQReader(this);
     120           2 :     scheduler->Enqueue(task);
     121             : }
     122             : 
     123       44492 : void ConfigAmqpClient::EnqueueUUIDRequest(string oper, string obj_type,
     124             :                                      string uuid_str) {
     125       44492 :     if (mgr_->config_json_parser()->IsReadObjectType(obj_type)) {
     126       44492 :         mgr_->EnqueueUUIDRequest(oper, obj_type, uuid_str);
     127             :     }
     128       44492 : }
     129             : 
     130         328 : string ConfigAmqpClient::FormAmqpUri(bool hide_auth_info) const {
     131         328 :     const string user = hide_auth_info ? "********" : rabbitmq_user();
     132         328 :     const string password = hide_auth_info ? "********" : rabbitmq_password();
     133         656 :     string uri = string("amqp://" + user + ":" +
     134        1312 :                         password + "@" + rabbitmq_ip() + ":" + rabbitmq_port());
     135         328 :     if (!rabbitmq_vhost().empty()) {
     136         106 :         if (rabbitmq_vhost().compare("/") != 0) {
     137         106 :             uri += "/" + rabbitmq_vhost();
     138             :         }
     139             :     }
     140         656 :     return uri;
     141         328 : }
     142             : 
     143         208 : void ConfigAmqpClient::ReportRabbitMQConnectionStatus(bool connected) const {
     144         208 :     if (connected) {
     145             :         // Update connection info
     146         312 :         process::ConnectionState::GetInstance()->Update(
     147             :             process::ConnectionType::DATABASE, "RabbitMQ",
     148             :             process::ConnectionStatus::UP,
     149         208 :             endpoints(), "RabbitMQ connection established");
     150         104 :         CONFIG_CLIENT_DEBUG(ConfigClientMgrDebug,
     151             :                             "RabbitMQ SM: RabbitMQ connection established");
     152             :     } else {
     153         312 :         process::ConnectionState::GetInstance()->Update(
     154             :             process::ConnectionType::DATABASE, "RabbitMQ",
     155             :             process::ConnectionStatus::DOWN,
     156         208 :             endpoints(), "RabbitMQ connection down");
     157         104 :         CONFIG_CLIENT_DEBUG(ConfigClientMgrDebug,
     158             :                             "RabbitMQ SM: RabbitMQ connection down");
     159             :     }
     160         208 : }
     161             : 
     162         104 : void ConfigAmqpClient::RabbitMQReader::ConnectToRabbitMQ(bool queue_delete) {
     163         104 :     amqpclient_->ReportRabbitMQConnectionStatus(false);
     164         104 :     amqpclient_->set_connected(false);
     165         104 :     string message = "RabbitMQ SM: Connect to Rabbit MQ with queue_delete ";
     166         104 :     message += queue_delete ? "TRUE" : "FALSE";
     167         104 :     CONFIG_CLIENT_DEBUG(ConfigClientMgrDebug, message);
     168         104 :     size_t count = 0;
     169             :     while (true) {
     170             :         // If we are signalled to stop, break now.
     171         104 :         if (amqpclient_->config_manager()->is_reinit_triggered()) {
     172           0 :             CONFIG_CLIENT_DEBUG(ConfigClientMgrDebug,
     173             :                                 "RabbitMQ SM: Skipped connect due to reinit");
     174           0 :             return;
     175             :         }
     176         104 :         string uri = amqpclient_->FormAmqpUri(false);
     177             :         try {
     178         104 :             if (amqpclient_->rabbitmq_use_ssl()) {
     179           0 :                 int port = boost::lexical_cast<int>(
     180           0 :                         amqpclient_->rabbitmq_port());
     181             : 
     182           0 :                 channel_->CreateSecure(
     183           0 :                     amqpclient_->rabbitmq_ssl_ca_certs(),
     184           0 :                     amqpclient_->rabbitmq_ip(),
     185           0 :                     amqpclient_->rabbitmq_ssl_keyfile(),
     186           0 :                     amqpclient_->rabbitmq_ssl_certfile(),
     187             :                     port,
     188           0 :                     amqpclient_->rabbitmq_user(),
     189           0 :                     amqpclient_->rabbitmq_password(),
     190           0 :                     amqpclient_->rabbitmq_vhost());
     191             :             } else {
     192         104 :                 channel_->CreateFromUri(uri);
     193             :             }
     194             :             // passive = false, durable = false, auto_delete = false
     195         104 :             channel_->DeclareExchange("vnc_config.object-update",
     196             :               AmqpClient::Channel::EXCHANGE_TYPE_FANOUT, false, false, false);
     197             :             string queue_name =
     198         208 :                 amqpclient_->module_name() + "." + amqpclient_->hostname();
     199             : 
     200         104 :             if (queue_delete) {
     201           2 :                 channel_->DeleteQueue(queue_name, false, false);
     202             :             }
     203             : 
     204             :             // passive = false, durable = false,
     205             :             // exclusive = false, auto_delete = false
     206         104 :             string queue = channel_->DeclareQueue(queue_name, false, false,
     207         104 :                                                   false, false);
     208         104 :             channel_->BindQueue(queue, "vnc_config.object-update");
     209             :             // no_local = true, no_ack = false,
     210             :             // exclusive = false, message_prefetch_count = 0
     211         208 :             consumer_tag_ = channel_->BasicConsume(queue, queue_name,
     212         104 :                                                    true, false, false, 0);
     213         104 :         } catch (std::exception &e) {
     214           0 :             static string what = e.what();
     215             :             string message =
     216             :                 "RabbitMQ SM: Caught exception while connecting to RabbitMQ: "
     217           0 :                 + amqpclient_->rabbitmq_ip() + ":"
     218           0 :                 + amqpclient_->rabbitmq_port() + " : " + what;
     219           0 :             cout << message << endl;
     220           0 :             CONFIG_CLIENT_WARN(ConfigClientMgrWarning, message);
     221           0 :             if (++count == amqpclient_->rabbitmq_server_list_len()) {
     222           0 :                 count = 0;
     223             :                 // Tried connecting to all given servers.. Now wait to reconnect
     224           0 :                 sleep(5);
     225             :             }
     226           0 :             amqpclient_->increment_rabbitmq_server_index();
     227           0 :             continue;
     228           0 :         } catch (...) {
     229             :             string message =
     230             :                 "RabbitMQ SM: Caught fatal exception while "
     231             :                 "connecting to RabbitMQ: "
     232           0 :                 + amqpclient_->rabbitmq_ip() + ":"
     233           0 :                 + amqpclient_->rabbitmq_port();
     234           0 :             cout << message << endl;
     235           0 :             CONFIG_CLIENT_WARN(ConfigClientMgrWarning, message);
     236           0 :             assert(0);
     237           0 :         }
     238             : 
     239         104 :         amqpclient_->ReportRabbitMQConnectionStatus(true);
     240         104 :         amqpclient_->set_connected(true);
     241         104 :         break;
     242         104 :     }
     243         104 : }
     244             : 
     245         208 : void ConfigAmqpClient::set_connected(bool connected) {
     246         208 :     connection_status_ = connected;
     247         208 :     connection_status_change_at_ = UTCTimestampUsec();
     248         208 : }
     249             : 
     250         224 : void ConfigAmqpClient::GetConnectionInfo(ConfigAmqpConnInfo &conn_info) const {
     251         224 :     conn_info.connection_status = connection_status_;
     252             :     conn_info.connection_status_change_at =
     253         224 :         UTCUsecToString(connection_status_change_at_);
     254         224 :     conn_info.url = FormAmqpUri(true);
     255         224 : }
     256             : 
     257         574 : bool ConfigAmqpClient::ProcessMessage(const string &json_message) {
     258         574 :     Document document;
     259         574 :     document.Parse<0>(json_message.c_str());
     260             : 
     261         574 :     if (document.HasParseError()) {
     262           2 :         size_t pos = document.GetErrorOffset();
     263             :         // GetParseError returns const char *
     264           2 :         cout << "Error in parsing JSON message from rabbitMQ at "
     265           2 :             << pos << "with error description"
     266           2 :             << document.GetParseError() << endl;
     267           2 :         return false;
     268             :     } else {
     269         572 :         string oper = "";
     270         572 :         string uuid_str = "";
     271         572 :         string obj_type = "";
     272         572 :         string obj_name = "";
     273         572 :         for (Value::ConstMemberIterator itr = document.MemberBegin();
     274        2860 :              itr != document.MemberEnd(); ++itr) {
     275        2288 :             string key(itr->name.GetString());
     276        2288 :             if (key == "oper") {
     277         572 :                 oper = itr->value.GetString();
     278        1716 :             } else if (key == "type") {
     279         572 :                 obj_type = itr->value.GetString();
     280        1144 :             } else if (key == "fq_name") {
     281         572 :                 if (!itr->value.IsArray())
     282          64 :                     continue;
     283         508 :                 ostringstream os;
     284         508 :                 SizeType sz = itr->value.GetArray().Size();
     285         508 :                 if (sz == 0)
     286           0 :                     continue;
     287        1002 :                 for (SizeType i = 0; i < sz-1; i++) {
     288         494 :                     os << itr->value[i].GetString() << ":";
     289             :                 }
     290         508 :                 os << itr->value[sz-1].GetString();
     291         508 :                 obj_name = os.str();
     292        1080 :             } else if (key == "uuid") {
     293         572 :                 uuid_str = itr->value.GetString();
     294             :             }
     295        2288 :         }
     296             : 
     297         572 :         if ((oper == "") || (uuid_str == "") || (obj_type == "")) {
     298           0 :             CONFIG_CLIENT_WARN(ConfigClientFQNameCache,
     299             :                 "Empty object name or empty type or empty uuid", obj_type,
     300             :                 obj_name, uuid_str);
     301           0 :             return false;
     302             :         }
     303             : 
     304         572 :         if ((oper == "CREATE") || (oper == "UPDATE")) {
     305         432 :             if (obj_name.empty()) {
     306           0 :                 CONFIG_CLIENT_WARN(ConfigClientFQNameCache,
     307             :                     "Empty object name during CREATE/UPDATE",
     308             :                     obj_type, obj_name, uuid_str);
     309           0 :                 return false;
     310             :             }
     311             : 
     312             :             // It is possible in some cases that RabbitMQ might club the 
     313             :             // CREATE and UPDATE for an object if they happen in quick 
     314             :             // succession (for instance control node failover). In such 
     315             :             // cases, we could only get an UPDATE of the object without 
     316             :             // a preceding CREATE. Handle the UPDATE as a CREATE and add
     317             :             //  object to FQNameCache.
     318             :             // In the unlikely event that we do receive a CREATE for the
     319             :             // same object after the UPDATE, we check if the FQNameCache
     320             :             // is already present before adding to it.
     321             :             // Also, it is ok to process the CREATE/UPDATE irrespective
     322             :             // of the order in which they are received since we always
     323             :             // read the uuid table from Cassandra.
     324             : 
     325             :             string stored_fq_name =
     326         432 :                 config_manager()->config_db_client()->FindFQName(uuid_str);
     327         432 :             if (stored_fq_name == "ERROR") {
     328             :                 // FQName Cache entry not present. Create one.
     329             :                 // Log the event if the operation is an UPDATE.
     330         432 :                 if (oper == "UPDATE") {
     331         160 :                     CONFIG_CLIENT_WARN(ConfigClientFQNameCache,
     332             :                             "FQ Name Cache entry not found on UPDATE:",
     333             :                             obj_type, obj_name, uuid_str);
     334             :                 }
     335         432 :                 config_manager()->config_db_client()->
     336         432 :                             AddFQNameCache(uuid_str, obj_type, obj_name);
     337             :             }
     338         572 :         } else if (oper == "DELETE") {
     339         140 :             config_manager()->config_db_client()->
     340         140 :                 InvalidateFQNameCache(uuid_str);
     341             :         }
     342             : 
     343         572 :         CONFIG_CLIENT_RABBIT_MSG_TRACE(ConfigClientRabbitMQMsgTrace, oper,
     344             :                                        obj_type, obj_name, uuid_str);
     345         572 :         EnqueueUUIDRequest(oper, obj_type, uuid_str);
     346         572 :     }
     347         572 :     return true;
     348         574 : }
     349             : 
     350         204 : bool ConfigAmqpClient::RabbitMQReader::ReceiveRabbitMessages(
     351             :                                      AmqpClient::Envelope::ptr_t &envelope) {
     352             :     try {
     353             :         // timeout = 10ms.. To handle SIGHUP on config changes
     354             :         // On reinit, config client manager will trigger the amqp client
     355             :         // to shutdown. Blocking wait without timeout will not allow this.
     356         204 :         channel_->BasicConsumeMessage(consumer_tag_, envelope, 10);
     357         102 :         return true;
     358         102 :     } catch (std::exception &e) {
     359         102 :         static string what = e.what();
     360             :         string message =
     361             :             "RabbitMQ SM: Caught exception while receiving "
     362             :             "messages from RabbitMQ: "
     363         204 :             + amqpclient_->rabbitmq_ip() + ":"
     364         306 :             + amqpclient_->rabbitmq_port() + " : " + what;
     365         102 :         cout << message << endl;
     366         102 :         CONFIG_CLIENT_WARN(ConfigClientMgrWarning, message);
     367         102 :         return false;
     368         102 :     } catch (...) {
     369             :         string message =
     370             :             "RabbitMQ SM: Caught fatal unknown exception while receiving "
     371             :             "messages from RabbitMQ "
     372           0 :             + amqpclient_->rabbitmq_ip() + ':'
     373           0 :             + amqpclient_->rabbitmq_port();
     374           0 :         cout << message << endl;
     375           0 :         CONFIG_CLIENT_WARN(ConfigClientMgrWarning, message);
     376           0 :         assert(0);
     377           0 :     }
     378             :     return true;
     379             : }
     380             : 
     381           0 : bool ConfigAmqpClient::RabbitMQReader::AckRabbitMessages(
     382             :                                      AmqpClient::Envelope::ptr_t &envelope) {
     383             :     try {
     384           0 :         channel_->BasicAck(envelope);
     385           0 :     } catch (std::exception &e) {
     386           0 :         static string what = e.what();
     387             :         string message =
     388             :             "RabbitMQ SM: Caught exception while acking "
     389             :             "messages from RabbitMQ: "
     390           0 :             + amqpclient_->rabbitmq_ip() + ':'
     391           0 :             + amqpclient_->rabbitmq_port() + ':' + what;
     392           0 :         cout << message << endl;
     393           0 :         CONFIG_CLIENT_WARN(ConfigClientMgrWarning, message);
     394           0 :         return false;
     395           0 :     } catch (...) {
     396             :         string message =
     397             :             "RabbitMQ SM: Caught fatal unknown exception while acking messages "
     398           0 :             "from RabbitMQ " + amqpclient_->rabbitmq_ip() + ':'
     399           0 :             + amqpclient_->rabbitmq_port();
     400           0 :         cout << message << endl;
     401           0 :         CONFIG_CLIENT_WARN(ConfigClientMgrWarning, message);
     402           0 :         assert(0);
     403           0 :     }
     404           0 :     return true;
     405             : }
     406             : 
     407             : 
     408           2 : bool ConfigAmqpClient::RabbitMQReader::Run() {
     409             :     // If reinit is triggerred, don't wait for end of config trigger
     410             :     // return from here to process reinit
     411           2 :     if (amqpclient_->config_manager()->is_reinit_triggered()) {
     412           0 :         CONFIG_CLIENT_DEBUG(
     413             :             ConfigClientMgrDebug,
     414             :             "RabbitMQ SM: Reinit triggered, don't wait for end of config");
     415           0 :         return true;
     416             :     }
     417             : 
     418             :     // To start consuming the message, we should have finised bulk sync
     419           2 :     amqpclient_->config_manager()->WaitForEndOfConfig();
     420             : 
     421             :     while (true) {
     422             :         // Test only
     423         206 :         if (amqpclient_->terminate())
     424           2 :             break;
     425             :         // If reinit is triggerred, break from the message receiving loop
     426         204 :         if (amqpclient_->config_manager()->is_reinit_triggered()) {
     427           0 :             CONFIG_CLIENT_DEBUG(ConfigClientMgrDebug,
     428             :             "RabbitMQ SM: Reinit triggered, break from message receiving loop");
     429           0 :             break;
     430             :         }
     431         204 :         AmqpClient::Envelope::ptr_t envelope;
     432         204 :         if (ReceiveRabbitMessages(envelope) == false) {
     433         102 :             ConnectToRabbitMQ(false);
     434         102 :             continue;
     435             :         }
     436             : 
     437         102 :         if (!envelope)
     438         102 :             continue;
     439             : 
     440           0 :         amqpclient_->ProcessMessage(envelope->Message()->Body());
     441           0 :         if (AckRabbitMessages(envelope) == false) {
     442           0 :             ConnectToRabbitMQ(false);
     443           0 :             continue;
     444             :         }
     445         408 :     }
     446           2 :     return true;
     447             : }

Generated by: LCOV version 1.14