/// Set up a new view of the memcached cluster(s). The view determines /// how data is distributed around the cluster. void BaseMemcachedStore::new_view(const MemcachedConfig& config) { TRC_STATUS("Updating memcached store configuration"); // Create a new view with the new server lists. MemcachedStoreView view(_vbuckets, _replicas); view.update(config); // Now copy the view so it can be accessed by the worker threads. pthread_rwlock_wrlock(&_view_lock); // Get the list of servers from the view. _servers = view.servers(); // For each vbucket, get the list of read replicas and write replicas. for (int ii = 0; ii < _vbuckets; ++ii) { _read_replicas[ii] = view.read_replicas(ii); _write_replicas[ii] = view.write_replicas(ii); } // Update the view number as the last thing here, otherwise we could stall // other threads waiting for the lock. TRC_STATUS("Finished preparing new view, so flag that workers should switch to it"); ++_view_number; pthread_rwlock_unlock(&_view_lock); }
void CommunicationMonitor::track_communication_changes(unsigned long now_ms) { if (_alarm == NULL) { return; } now_ms = now_ms ? now_ms : current_time_ms(); if (now_ms > _next_check) { // Current time has passed our monitor interval time, so take the lock // and see if we are the lucky thread that gets to check for an alarm // condition. pthread_mutex_lock(&_lock); // If current time is still past the monitor interval time we are the // the lucky one, otherwise somebody beat us to the punch (so just drop // the lock and return). if (now_ms > _next_check) { // Grab the current counts and reset them to zero in a lockless manner. TRC_DEBUG("Check communication monitor state for alarm %d", _alarm->index()); unsigned int succeeded = _succeeded.fetch_and(0); unsigned int failed = _failed.fetch_and(0); if (!_alarm->alarmed()) { // A communication alarm is not currently set so see if one needs to // be. This will be the case if there were no successful comms over // the interval, and at least one failed comm. TRC_DEBUG("Alarm currently clear - successful attempts %d, failures %d", succeeded, failed); if ((succeeded == 0) && (failed != 0)) { TRC_STATUS("Setting alarm %d", _alarm->index()); _alarm->set(); } } else { // A communication alarm is currently set so see if it needs to be // cleared. This will be the case if at lease one successful comm // was reported over the interval. TRC_DEBUG("Alarm currently set - successful attempts %d", succeeded); if (succeeded != 0) { TRC_STATUS("Clearing alarm %d", _alarm->index()); _alarm->clear(); } } _next_check = (_alarm->alarmed()) ? now_ms + _clear_confirm_ms : now_ms + _set_confirm_ms ; } pthread_mutex_unlock(&_lock); } }
ConnectionPool::ConnectionPool(pjsip_host_port* target, int num_connections, int recycle_period, pj_pool_t* pool, pjsip_endpoint* endpt, pjsip_tpfactory* tp_factory, SNMP::IPCountTable* sprout_count_tbl) : _target(*target), _num_connections(num_connections), _recycle_period(recycle_period), _recycle_margin((recycle_period * RECYCLE_RANDOM_MARGIN)/100), _pool(pool), _endpt(endpt), _tpfactory(tp_factory), _recycler(NULL), _terminated(false), _active_connections(0), _sprout_count_tbl(sprout_count_tbl) { TRC_STATUS("Creating connection pool to %.*s:%d", _target.host.slen, _target.host.ptr, _target.port); TRC_STATUS(" connections = %d, recycle time = %d +/- %d seconds", _num_connections, _recycle_period, _recycle_margin); pthread_mutex_init(&_tp_hash_lock, NULL); _tp_hash.resize(_num_connections); }
void Store::configure_connection(std::string cass_hostname, uint16_t cass_port, CommunicationMonitor* comm_monitor) { TRC_STATUS("Configuring store connection"); TRC_STATUS(" Hostname: %s", cass_hostname.c_str()); TRC_STATUS(" Port: %u", cass_port); _cass_hostname = cass_hostname; _cass_port = cass_port; _comm_monitor = comm_monitor; }
void Store::configure_workers(ExceptionHandler* exception_handler, unsigned int num_threads, unsigned int max_queue) { TRC_STATUS("Configuring store worker pool"); TRC_STATUS(" Threads: %u", num_threads); TRC_STATUS(" Max Queue: %u", max_queue); _exception_handler = exception_handler; _num_threads = num_threads; _max_queue = max_queue; }
LoadMonitor::LoadMonitor(int init_target_latency, int max_bucket_size, float init_token_rate, float init_min_token_rate, SNMP::ContinuousAccumulatorTable* token_rate_table, SNMP::U32Scalar* smoothed_latency_scalar, SNMP::U32Scalar* target_latency_scalar, SNMP::U32Scalar* penalties_scalar, SNMP::U32Scalar* token_rate_scalar) : bucket(max_bucket_size, init_token_rate), _token_rate_table(token_rate_table), _smoothed_latency_scalar(smoothed_latency_scalar), _target_latency_scalar(target_latency_scalar), _penalties_scalar(penalties_scalar), _token_rate_scalar(token_rate_scalar) { pthread_mutexattr_t attrs; pthread_mutexattr_init(&attrs); pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&_lock, &attrs); pthread_mutexattr_destroy(&attrs); TRC_STATUS("Constructing LoadMonitor"); TRC_STATUS(" Target latency (usecs) : %d", init_target_latency); TRC_STATUS(" Max bucket size : %d", max_bucket_size); TRC_STATUS(" Initial token fill rate/s: %f", init_token_rate); TRC_STATUS(" Min token fill rate/s : %f", init_min_token_rate); REQUESTS_BEFORE_ADJUSTMENT = 20; SECONDS_BEFORE_ADJUSTMENT = 2; // Adjustment parameters for token bucket DECREASE_THRESHOLD = 0.0; DECREASE_FACTOR = 1.2; INCREASE_THRESHOLD = -0.005; INCREASE_FACTOR = 0.5; accepted = 0; rejected = 0; penalties = 0; pending_count = 0; max_pending_count = 0; target_latency = init_target_latency; smoothed_latency = init_target_latency; adjust_count = 0; timespec current_time; clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t_time); last_adjustment_time_ms = (current_time.tv_sec * 1000) + (current_time.tv_nsec / 1000); min_token_rate = init_min_token_rate; // As this statistics reporting is continuous, we should // publish the statistics when initialised. update_statistics(); }
ResultCode Store::connection_test() { ResultCode rc = OK; // Check that we can connect to cassandra by getting a client. This logs in // and switches to the specified keyspace, so is a good test of whether // cassandra is working properly. TRC_STATUS("Starting store"); try { get_client(); release_client(); } catch(TTransportException te) { TRC_ERROR("Store caught TTransportException: %s", te.what()); rc = CONNECTION_ERROR; } catch(NotFoundException nfe) { TRC_ERROR("Store caught NotFoundException: %s", nfe.what()); rc = NOT_FOUND; } catch(...) { TRC_ERROR("Store caught unknown exception!"); rc = UNKNOWN_ERROR; } return rc; }
/// Loads the MMTEL AS plug-in, returning the supported Sproutlets. bool MMTELASPlugin::load(struct options& opt, std::list<Sproutlet*>& sproutlets) { bool plugin_loaded = true; SNMP::SuccessFailCountByRequestTypeTable* incoming_sip_transactions = SNMP::SuccessFailCountByRequestTypeTable::create("mmtel_as_incoming_sip_transactions", "1.2.826.0.1.1578918.9.3.24"); SNMP::SuccessFailCountByRequestTypeTable* outgoing_sip_transactions = SNMP::SuccessFailCountByRequestTypeTable::create("mmtel_as_outgoing_sip_transactions", "1.2.826.0.1.1578918.9.3.25"); if (opt.xdm_server != "") { // Create a connection to the XDMS. TRC_STATUS("Creating connection to XDMS %s", opt.xdm_server.c_str()); _xdm_cxn_count_tbl = SNMP::IPCountTable::create("homer-ip-count", ".1.2.826.0.1.1578918.9.3.2.1"); _xdm_latency_tbl = SNMP::AccumulatorTable::create("homer-latency", ".1.2.826.0.1.1578918.9.3.2.2"); _xdm_connection = new XDMConnection(opt.xdm_server, http_resolver, load_monitor, _xdm_cxn_count_tbl, _xdm_latency_tbl); // Load the MMTEL AppServer _mmtel = new Mmtel("mmtel", _xdm_connection); _mmtel_sproutlet = new SproutletAppServerShim(_mmtel, incoming_sip_transactions, outgoing_sip_transactions, "mmtel." + opt.home_domain); sproutlets.push_back(_mmtel_sproutlet); } return plugin_loaded; }
ResultCode Store::start() { ResultCode rc = OK; // Start the store. We don't check for connectivity to Cassandra at this // point as some store users want the store to load even when Cassandra has // failed (it will recover later). If a store user cares about the status // of Cassandra it should use the test() method. TRC_STATUS("Starting store"); // Start the thread pool. if (_num_threads > 0) { _thread_pool = new Pool(this, _num_threads, _exception_handler, _max_queue); if (!_thread_pool->start()) { rc = RESOURCE_ERROR; // LCOV_EXCL_LINE } } return rc; }
void Store::stop() { TRC_STATUS("Stopping store"); if (_thread_pool != NULL) { _thread_pool->stop(); } }
void Store::wait_stopped() { TRC_STATUS("Waiting for store to stop"); if (_thread_pool != NULL) { _thread_pool->join(); delete _thread_pool; _thread_pool = NULL; } }
HttpResolver::HttpResolver(DnsCachedResolver* dns_client, int address_family, int blacklist_duration) : BaseResolver(dns_client), _address_family(address_family) { TRC_DEBUG("Creating HTTP resolver"); // Create the blacklist. create_blacklist(blacklist_duration); TRC_STATUS("Created HTTP resolver"); }
/// Loads the I-CSCF plug-in, returning the supported Sproutlets. bool ICSCFPlugin::load(struct options& opt, std::list<Sproutlet*>& sproutlets) { bool plugin_loaded = true; // Create the SNMP tables here - they should exist based on whether the // plugin is loaded, not whether the Sproutlet is enabled, in order to // simplify SNMP polling of multiple differently-configured Sprout nodes. _incoming_sip_transactions_tbl = SNMP::SuccessFailCountByRequestTypeTable::create("icscf_incoming_sip_transactions", "1.2.826.0.1.1578918.9.3.18"); _outgoing_sip_transactions_tbl = SNMP::SuccessFailCountByRequestTypeTable::create("icscf_outgoing_sip_transactions", "1.2.826.0.1.1578918.9.3.19"); if (opt.enabled_icscf) { TRC_STATUS("I-CSCF plugin enabled"); // Create the S-CSCF selector. _scscf_selector = new SCSCFSelector(opt.uri_scscf); // Create the I-CSCF ACR factory. _acr_factory = (ralf_processor != NULL) ? (ACRFactory*)new RalfACRFactory(ralf_processor, ACR::ICSCF) : new ACRFactory(); // Create the I-CSCF sproutlet. _icscf_sproutlet = new ICSCFSproutlet(opt.prefix_icscf, opt.uri_bgcf, opt.port_icscf, opt.uri_icscf, hss_connection, _acr_factory, _scscf_selector, enum_service, _incoming_sip_transactions_tbl, _outgoing_sip_transactions_tbl, opt.override_npdi); _icscf_sproutlet->init(); sproutlets.push_back(_icscf_sproutlet); } return plugin_loaded; }
SIPResolver::SIPResolver(DnsCachedResolver* dns_client, int blacklist_duration) : BaseResolver(dns_client) { TRC_DEBUG("Creating SIP resolver"); // Create the NAPTR cache. std::map<std::string, int> naptr_services; naptr_services["SIP+D2U"] = IPPROTO_UDP; naptr_services["SIP+D2T"] = IPPROTO_TCP; create_naptr_cache(naptr_services); // Create the SRV cache. create_srv_cache(); // Create the blacklist. create_blacklist(blacklist_duration); TRC_STATUS("Created SIP resolver"); }
void ConnectionPool::recycle_connections() { // The recycler periodically recycles the connections so that any new nodes // in the upstream proxy cluster get used reasonably soon after they are // active. To avoid mucking around with variable length waits, the // algorithm waits for a fixed period (one second) then recycles connections // that are due to be recycled. while (!_terminated) { #ifdef UNIT_TEST // A smaller pause here is much faster sleep(0.1); #else sleep(1); #endif int now = time(NULL); // Walk the vector of connections. This is safe to do without the lock // because the vector is immutable. for (size_t ii = 0; ii < _tp_hash.size(); ++ii) { if (_tp_hash[ii].tp == NULL) { // This slot is empty, so try to populate it now. create_connection(ii); } else if ((_tp_hash[ii].connected) && (_tp_hash[ii].recycle_time != 0) && (now >= _tp_hash[ii].recycle_time)) { // This slot is due to be recycled, so quiesce the existing // connection and create a new one. TRC_STATUS("Recycle TCP connection slot %d", ii); quiesce_connection(ii); create_connection(ii); } } } }
// Set up the SNMP agent. Returns 0 if it succeeds. int snmp_setup(const char* name) { // Make sure we start as a subagent, not a master agent. netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1); // Use callback-based logging, and integrate it with the Clearwater logger snmp_enable_calllog(); snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING, logging_callback, NULL); netsnmp_container_init_list(); int rc = init_agent(name); if (rc != 0) { TRC_WARNING("SNMP AgentX initialization failed"); } else { TRC_STATUS("AgentX agent initialised"); } return rc; }
void SCSCFSelector::update_scscf() { // Check whether the file exists. struct stat s; if ((stat(_configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No S-CSCF configuration data (file %s does not exist)", _configuration.c_str()); CL_SPROUT_SCSCF_FILE_MISSING.log(); return; } TRC_STATUS("Loading S-CSCF configuration from %s", _configuration.c_str()); // Read from the file std::ifstream fs(_configuration.c_str()); std::string scscf_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (scscf_str == "") { // LCOV_EXCL_START TRC_ERROR("Failed to read S-CSCF configuration data from %s", _configuration.c_str()); CL_SPROUT_SCSCF_FILE_EMPTY.log(); return; // LCOV_EXCL_STOP } // Now parse the document rapidjson::Document doc; doc.Parse<0>(scscf_str.c_str()); if (doc.HasParseError()) { TRC_ERROR("Failed to read S-CSCF configuration data: %s\nError: %s", scscf_str.c_str(), rapidjson::GetParseError_En(doc.GetParseError())); CL_SPROUT_SCSCF_FILE_INVALID.log(); return; } try { std::vector<scscf_t> new_scscfs; JSON_ASSERT_CONTAINS(doc, "s-cscfs"); JSON_ASSERT_ARRAY(doc["s-cscfs"]); const rapidjson::Value& scscfs_arr = doc["s-cscfs"]; for (rapidjson::Value::ConstValueIterator scscfs_it = scscfs_arr.Begin(); scscfs_it != scscfs_arr.End(); ++scscfs_it) { try { scscf_t new_scscf; JSON_GET_STRING_MEMBER(*scscfs_it, "server", new_scscf.server); JSON_GET_INT_MEMBER(*scscfs_it, "priority", new_scscf.priority); JSON_GET_INT_MEMBER(*scscfs_it, "weight", new_scscf.weight); JSON_ASSERT_CONTAINS(*scscfs_it, "capabilities"); JSON_ASSERT_ARRAY((*scscfs_it)["capabilities"]); const rapidjson::Value& cap_arr = (*scscfs_it)["capabilities"]; std::vector<int> capabilities_vec; for (rapidjson::Value::ConstValueIterator cap_it = cap_arr.Begin(); cap_it != cap_arr.End(); ++cap_it) { capabilities_vec.push_back((*cap_it).GetInt()); } // Sort the capabilities and remove duplicates std::sort(capabilities_vec.begin(), capabilities_vec.end()); capabilities_vec.erase(unique(capabilities_vec.begin(), capabilities_vec.end()), capabilities_vec.end() ); new_scscf.capabilities = capabilities_vec; new_scscfs.push_back(new_scscf); capabilities_vec.clear(); } catch (JsonFormatError err) { // Badly formed number block. TRC_WARNING("Badly formed S-CSCF entry (hit error at %s:%d)", err._file, err._line); CL_SPROUT_SCSCF_FILE_INVALID.log(); } } // Take a write lock on the mutex in RAII style boost::lock_guard<boost::shared_mutex> write_lock(_scscfs_rw_lock); _scscfs = new_scscfs; } catch (JsonFormatError err) { TRC_ERROR("Badly formed S-CSCF configuration file - missing s-cscfs object"); CL_SPROUT_SCSCF_FILE_INVALID.log(); } }
int main(int argc, char**argv) { // Set up our exception signal handler for asserts and segfaults. signal(SIGABRT, signal_handler); signal(SIGSEGV, signal_handler); sem_init(&term_sem, 0, 0); signal(SIGTERM, terminate_handler); struct options options; options.local_host = "127.0.0.1"; options.http_address = "0.0.0.0"; options.http_port = 11888; options.http_threads = 1; options.http_worker_threads = 50; options.homestead_http_name = "homestead-http-name.unknown"; options.digest_timeout = 300; options.home_domain = "home.domain"; options.sas_server = "0.0.0.0"; options.sas_system_name = ""; options.access_log_enabled = false; options.log_to_file = false; options.log_level = 0; options.memcached_write_format = MemcachedWriteFormat::JSON; options.target_latency_us = 100000; options.max_tokens = 1000; options.init_token_rate = 100.0; options.min_token_rate = 10.0; options.exception_max_ttl = 600; options.http_blacklist_duration = HttpResolver::DEFAULT_BLACKLIST_DURATION; options.pidfile = ""; options.daemon = false; if (init_logging_options(argc, argv, options) != 0) { return 1; } Log::setLoggingLevel(options.log_level); if ((options.log_to_file) && (options.log_directory != "")) { // Work out the program name from argv[0], stripping anything before the final slash. char* prog_name = argv[0]; char* slash_ptr = rindex(argv[0], '/'); if (slash_ptr != NULL) { prog_name = slash_ptr + 1; } Log::setLogger(new Logger(options.log_directory, prog_name)); } TRC_STATUS("Log level set to %d", options.log_level); std::stringstream options_ss; for (int ii = 0; ii < argc; ii++) { options_ss << argv[ii]; options_ss << " "; } std::string options_str = "Command-line options were: " + options_ss.str(); TRC_INFO(options_str.c_str()); if (init_options(argc, argv, options) != 0) { return 1; } if (options.daemon) { // Options parsed and validated, time to demonize before writing out our // pidfile or spwaning threads. int errnum = Utils::daemonize(); if (errnum != 0) { TRC_ERROR("Failed to convert to daemon, %d (%s)", errnum, strerror(errnum)); exit(0); } } if (options.pidfile != "") { int rc = Utils::lock_and_write_pidfile(options.pidfile); if (rc == -1) { // Failure to acquire pidfile lock TRC_ERROR("Could not write pidfile - exiting"); return 2; } } start_signal_handlers(); AccessLogger* access_logger = NULL; if (options.access_log_enabled) { TRC_STATUS("Access logging enabled to %s", options.access_log_directory.c_str()); access_logger = new AccessLogger(options.access_log_directory); } HealthChecker* hc = new HealthChecker(); hc->start_thread(); // Create an exception handler. The exception handler doesn't need // to quiesce the process before killing it. exception_handler = new ExceptionHandler(options.exception_max_ttl, false, hc); SAS::init(options.sas_system_name, "memento", SASEvent::CURRENT_RESOURCE_BUNDLE, options.sas_server, sas_write, create_connection_in_management_namespace); // Ensure our random numbers are unpredictable. unsigned int seed; seed = time(NULL) ^ getpid(); srand(seed); // Create alarm and communication monitor objects for the conditions // reported by memento. CommunicationMonitor* mc_comm_monitor = new CommunicationMonitor(new Alarm("memento", AlarmDef::MEMENTO_MEMCACHED_COMM_ERROR, AlarmDef::CRITICAL), "Memento", "Memcached"); Alarm* mc_vbucket_alarm = new Alarm("memento", AlarmDef::MEMENTO_MEMCACHED_VBUCKET_ERROR, AlarmDef::MAJOR); CommunicationMonitor* hs_comm_monitor = new CommunicationMonitor(new Alarm("memento", AlarmDef::MEMENTO_HOMESTEAD_COMM_ERROR, AlarmDef::CRITICAL), "Memento", "Homestead"); CommunicationMonitor* cass_comm_monitor = new CommunicationMonitor(new Alarm("memento", AlarmDef::MEMENTO_CASSANDRA_COMM_ERROR, AlarmDef::CRITICAL), "Memento", "Cassandra"); TRC_DEBUG("Starting alarm request agent"); AlarmReqAgent::get_instance().start(); MemcachedStore* m_store = new MemcachedStore(true, "./cluster_settings", mc_comm_monitor, mc_vbucket_alarm); AuthStore::SerializerDeserializer* serializer; std::vector<AuthStore::SerializerDeserializer*> deserializers; if (options.memcached_write_format == MemcachedWriteFormat::JSON) { serializer = new AuthStore::JsonSerializerDeserializer(); } else { serializer = new AuthStore::BinarySerializerDeserializer(); } deserializers.push_back(new AuthStore::JsonSerializerDeserializer()); deserializers.push_back(new AuthStore::BinarySerializerDeserializer()); AuthStore* auth_store = new AuthStore(m_store, serializer, deserializers, options.digest_timeout); LoadMonitor* load_monitor = new LoadMonitor(options.target_latency_us, options.max_tokens, options.init_token_rate, options.min_token_rate); LastValueCache* stats_aggregator = new MementoLVC(); // Create a DNS resolver and an HTTP specific resolver. int af = AF_INET; struct in6_addr dummy_addr; if (inet_pton(AF_INET6, options.local_host.c_str(), &dummy_addr) == 1) { TRC_DEBUG("Local host is an IPv6 address"); af = AF_INET6; } DnsCachedResolver* dns_resolver = new DnsCachedResolver("127.0.0.1"); HttpResolver* http_resolver = new HttpResolver(dns_resolver, af, options.http_blacklist_duration); HomesteadConnection* homestead_conn = new HomesteadConnection(options.homestead_http_name, http_resolver, load_monitor, hs_comm_monitor); // Create and start the call list store. CallListStore::Store* call_list_store = new CallListStore::Store(); call_list_store->configure_connection("localhost", 9160, cass_comm_monitor); // Test Cassandra connectivity. CassandraStore::ResultCode store_rc = call_list_store->connection_test(); if (store_rc == CassandraStore::OK) { // Store can connect to Cassandra, so start it. store_rc = call_list_store->start(); } if (store_rc != CassandraStore::OK) { TRC_ERROR("Unable to create call list store (RC = %d)", store_rc); exit(3); } HttpStack* http_stack = HttpStack::get_instance(); HttpStackUtils::SimpleStatsManager stats_manager(stats_aggregator); CallListTask::Config call_list_config(auth_store, homestead_conn, call_list_store, options.home_domain, stats_aggregator, hc, options.api_key); MementoSasLogger sas_logger; HttpStackUtils::PingHandler ping_handler; HttpStackUtils::SpawningHandler<CallListTask, CallListTask::Config> call_list_handler(&call_list_config, &sas_logger); HttpStackUtils::HandlerThreadPool pool(options.http_worker_threads, exception_handler); try { http_stack->initialize(); http_stack->configure(options.http_address, options.http_port, options.http_threads, exception_handler, access_logger, load_monitor, &stats_manager); http_stack->register_handler("^/ping$", &ping_handler); http_stack->register_handler("^/org.projectclearwater.call-list/users/[^/]*/call-list.xml$", pool.wrap(&call_list_handler)); http_stack->start(); } catch (HttpStack::Exception& e) { TRC_ERROR("Failed to initialize HttpStack stack - function %s, rc %d", e._func, e._rc); exit(2); } TRC_STATUS("Start-up complete - wait for termination signal"); sem_wait(&term_sem); TRC_STATUS("Termination signal received - terminating"); try { http_stack->stop(); http_stack->wait_stopped(); } catch (HttpStack::Exception& e) { TRC_ERROR("Failed to stop HttpStack stack - function %s, rc %d", e._func, e._rc); } call_list_store->stop(); call_list_store->wait_stopped(); hc->stop_thread(); delete homestead_conn; homestead_conn = NULL; delete call_list_store; call_list_store = NULL; delete http_resolver; http_resolver = NULL; delete dns_resolver; dns_resolver = NULL; delete load_monitor; load_monitor = NULL; delete auth_store; auth_store = NULL; delete call_list_store; call_list_store = NULL; delete m_store; m_store = NULL; delete exception_handler; exception_handler = NULL; delete hc; hc = NULL; // Stop the alarm request agent AlarmReqAgent::get_instance().stop(); delete mc_comm_monitor; mc_comm_monitor = NULL; delete mc_vbucket_alarm; mc_vbucket_alarm = NULL; delete hs_comm_monitor; hs_comm_monitor = NULL; delete cass_comm_monitor; cass_comm_monitor = NULL; SAS::term(); signal(SIGTERM, SIG_DFL); sem_destroy(&term_sem); }
void SIFCService::update_sets() { // Check whether the file exists. struct stat s; TRC_DEBUG("stat(%s) returns %d", _configuration.c_str(), stat(_configuration.c_str(), &s)); if ((stat(_configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No shared iFCs configuration (file %s does not exist)", _configuration.c_str()); CL_SPROUT_SIFC_FILE_MISSING.log(); set_alarm(); return; } TRC_STATUS("Loading shared iFCs configuration from %s", _configuration.c_str()); // Read from the file std::ifstream fs(_configuration.c_str()); std::string sifc_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (sifc_str == "") { TRC_ERROR("Failed to read shared iFCs configuration data from %s", _configuration.c_str()); CL_SPROUT_SIFC_FILE_EMPTY.log(); set_alarm(); return; } // Now parse the document rapidxml::xml_document<>* root = new rapidxml::xml_document<>; try { root->parse<0>(root->allocate_string(sifc_str.c_str())); } catch (rapidxml::parse_error& err) { TRC_ERROR("Failed to parse the shared iFCs configuration data:\n %s\n %s", sifc_str.c_str(), err.what()); CL_SPROUT_SIFC_FILE_INVALID_XML.log(); set_alarm(); delete root; root = NULL; return; } if (!root->first_node(SIFCService::SHARED_IFCS_SETS)) { TRC_ERROR("Invalid shared iFCs configuration file - missing SharedIFCsSets block"); CL_SPROUT_SIFC_FILE_MISSING_SHARED_IFCS_SETS.log(); set_alarm(); delete root; root = NULL; return; } // At this point, we're definitely going to override the iFCs we've got. // Update our map, taking a lock while we do so. boost::lock_guard<boost::shared_mutex> write_lock(_sets_rw_lock); _shared_ifc_sets.clear(); bool any_errors = false; rapidxml::xml_node<>* sets = root->first_node(SIFCService::SHARED_IFCS_SETS); rapidxml::xml_node<>* set = NULL; for (set = sets->first_node(SIFCService::SHARED_IFCS_SET); set != NULL; set = set->next_sibling(SIFCService::SHARED_IFCS_SET)) { rapidxml::xml_node<>* set_id_node = set->first_node(SIFCService::SET_ID); if (!set_id_node) { TRC_ERROR("Invalid shared iFC block - missing SetID. Skipping this entry"); CL_SPROUT_SIFC_FILE_MISSING_SET_ID.log(); any_errors = true; continue; } std::string set_id_str = std::string(set_id_node->value()); Utils::trim(set_id_str); int32_t set_id = std::atoi(set_id_str.c_str()); if (set_id_str != std::to_string(set_id)) { TRC_ERROR("Invalid shared iFC block - SetID (%s) isn't an int. Skipping this entry", set_id_str.c_str()); CL_SPROUT_SIFC_FILE_INVALID_SET_ID.log(set_id_str.c_str()); any_errors = true; continue; } if (_shared_ifc_sets.count(set_id) != 0) { TRC_ERROR("Invalid shared iFC block - SetID (%d) is repeated. Skipping this entry", set_id); CL_SPROUT_SIFC_FILE_REPEATED_SET_ID.log(set_id_str.c_str()); any_errors = true; continue; } std::vector<std::pair<int32_t, std::string>> ifc_set; for (rapidxml::xml_node<>* ifc = set->first_node(RegDataXMLUtils::IFC); ifc != NULL; ifc = ifc->next_sibling(RegDataXMLUtils::IFC)) { int32_t priority = 0; rapidxml::xml_node<>* priority_node = ifc->first_node(RegDataXMLUtils::PRIORITY); if (priority_node) { std::string priority_str = std::string(priority_node->value()); Utils::trim(priority_str); priority = std::atoi(priority_str.c_str()); if (priority_str != std::to_string(priority)) { TRC_ERROR("Invalid shared iFC block - Priority (%s) isn't an int. Skipping this entry", priority_str.c_str()); CL_SPROUT_SIFC_FILE_INVALID_PRIORITY.log(priority_str.c_str()); any_errors = true; continue; } } // Creating the iFC always passes; we don't validate the iFC any further // at this stage. We've validated this against a schema before allowing // any upload though. std::string ifc_str; rapidxml::print(std::back_inserter(ifc_str), *ifc, 0); ifc_set.push_back(std::make_pair(priority, ifc_str)); } TRC_STATUS("Adding %lu iFCs for ID %d", ifc_set.size(), set_id); _shared_ifc_sets.insert(std::make_pair(set_id, ifc_set)); } if (any_errors) { set_alarm(); } else { clear_alarm(); } delete root; root = NULL; }
void LoadMonitor::request_complete(int latency) { pthread_mutex_lock(&_lock); pending_count -= 1; smoothed_latency = (7 * smoothed_latency + latency) / 8; adjust_count += 1; if (adjust_count >= REQUESTS_BEFORE_ADJUSTMENT) { // We've seen the right number of requests, but ensure // that an appropriate amount of time has passed, so the rate doesn't // fluctuate wildly if latency spikes for a few milliseconds timespec current_time; clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t_time); unsigned long current_time_ms = (current_time.tv_sec * 1000) + (current_time.tv_nsec / 1000); if (current_time_ms >= (last_adjustment_time_ms + (SECONDS_BEFORE_ADJUSTMENT * 1000))) { // This algorithm is based on the Welsh and Culler "Adaptive Overload // Control for Busy Internet Servers" paper, although based on a smoothed // mean latency, rather than the 90th percentile as per the paper. // Also, the additive increase is scaled as a proportion of the maximum // bucket size, rather than an absolute number as per the paper. float err = ((float) (smoothed_latency - target_latency)) / target_latency; // Work out the percentage of accepted requests (for logs) float accepted_percent = (accepted + rejected == 0) ? 100.0 : 100 * (((float) accepted) / (accepted + rejected)); TRC_INFO("Accepted %f%% of requests, latency error = %f, overload responses = %d", accepted_percent, err, penalties); // latency is above where we want it to be, or we are getting overload responses from // Homer/Homestead, so adjust the rate downwards by a multiplicative factor if (err > DECREASE_THRESHOLD || penalties > 0) { float new_rate = bucket.rate / DECREASE_FACTOR; if (new_rate < min_token_rate) { new_rate = min_token_rate; } bucket.update_rate(new_rate); TRC_STATUS("Maximum incoming request rate/second decreased to %f " "(based on a smoothed mean latency of %d and %d upstream overload responses)", bucket.rate, smoothed_latency, penalties); } else if (err < INCREASE_THRESHOLD) { // Our latency is below the threshold, so increasing our permitted request rate would be // sensible. Before doing that, we check that we're using a significant proportion of our // current rate - if we're allowing 100 requests/sec, and we get 1 request/sec because it's // a quiet period, then it's going to be handled quickly, but that's not sufficient evidence // to increase our rate. float maximum_permitted_requests = bucket.rate * (current_time_ms - last_adjustment_time_ms) / 1000; // Arbitrary threshold - require 50% of our current permitted rate to be used float minimum_threshold = maximum_permitted_requests * 0.5; if (accepted > minimum_threshold) { float new_rate = bucket.rate + (-1 * err * bucket.max_size * INCREASE_FACTOR); bucket.update_rate(new_rate); TRC_STATUS("Maximum incoming request rate/second increased to %f " "(based on a smoothed mean latency of %d and %d upstream overload responses)", bucket.rate, smoothed_latency, penalties); } else { TRC_STATUS("Maximum incoming request rate/second unchanged - only handled %d requests" " recently, minimum threshold for a change is %f", accepted, minimum_threshold); } } else { TRC_DEBUG("Maximum incoming request rate/second is unchanged at %f", bucket.rate); } update_statistics(); // Reset counts last_adjustment_time_ms = current_time_ms; adjust_count = 0; accepted = 0; rejected = 0; penalties = 0; } } pthread_mutex_unlock(&_lock); }
/// Load the plug-ins, returning the resulting list of Sproutlets. bool PluginLoader::load(std::list<Sproutlet*>& sproutlets) { TRC_STATUS("Loading plug-ins from %s", _path.c_str()); bool plugins_loaded = true; DIR* d = opendir(_path.c_str()); if (d != NULL) { struct dirent *de; while ((de = readdir(d)) != NULL) { // The file name isn't a reliable indication that the file is a shared // object, but checking for a ".so" extension filters out files like "." // and ".." and prevents spurious error logs. If a file isn't a valid // shared object, dlopen will return NULL and we'll log an error. // We don't check the file type as given by de->d_type - this would also // filter out directories like "." and "..", but some filesystems like // XFS don't support this. Plugin p; p.name = _path + "/"; p.name.append(de->d_name); p.handle = NULL; p.plugin = NULL; if (p.name.compare(p.name.length() - 3, 3, ".so") != 0) { TRC_DEBUG("Skipping %s - doesn't have .so extension", p.name.c_str()); continue; } TRC_STATUS("Attempt to load plug-in %s", p.name.c_str()); dlerror(); p.handle = dlopen(p.name.c_str(), RTLD_NOW); if (p.handle != NULL) { p.plugin = static_cast<SproutletPlugin*>(dlsym(p.handle, "sproutlet_plugin")); if (p.plugin != NULL) { std::list<Sproutlet*> plugin_sproutlets; plugins_loaded = p.plugin->load(_opt, plugin_sproutlets); if (!plugins_loaded) { // There was an error loading one of the plugins. Return an error // now so that Sprout is killed, rather than running with // unexpected plugins. TRC_ERROR("Failed to successfully load plug-in %s", p.name.c_str()); break; } for (std::list<Sproutlet*>::const_iterator i = plugin_sproutlets.begin(); i != plugin_sproutlets.end(); ++i) { Sproutlet* s = *i; TRC_DEBUG("Sproutlet %s using API version %d", s->service_name().c_str(), s->api_version()); if (api_supported(s->api_version())) { // The API version required by the sproutlet is supported. sproutlets.push_back(s); TRC_STATUS("Loaded sproutlet %s using API version %d", s->service_name().c_str(), s->api_version()); } else { // The API version required by the sproutlet is not supported. TRC_ERROR("Sproutlet %s requires unsupported API version %d", s->service_name().c_str(), s->api_version()); } } } } if (p.plugin != NULL) { // Add shared object to the list of loaded plugins. _loaded.push_back(p); } else { TRC_ERROR("Error loading Sproutlet plug-in %s - %s", p.name.c_str(), dlerror()); if (p.handle != NULL) { dlclose(p.handle); } } } closedir(d); } TRC_STATUS("Finished loading plug-ins"); return plugins_loaded; }
void BgcfService::update_routes() { // Check whether the file exists. struct stat s; TRC_DEBUG("stat(%s) returns %d", _configuration.c_str(), stat(_configuration.c_str(), &s)); if ((stat(_configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No BGCF configuration (file %s does not exist)", _configuration.c_str()); CL_SPROUT_BGCF_FILE_MISSING.log(); return; } TRC_STATUS("Loading BGCF configuration from %s", _configuration.c_str()); // Read from the file std::ifstream fs(_configuration.c_str()); std::string bgcf_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (bgcf_str == "") { // LCOV_EXCL_START TRC_ERROR("Failed to read BGCF configuration data from %s", _configuration.c_str()); CL_SPROUT_BGCF_FILE_EMPTY.log(); return; // LCOV_EXCL_STOP } // Now parse the document rapidjson::Document doc; doc.Parse<0>(bgcf_str.c_str()); if (doc.HasParseError()) { TRC_ERROR("Failed to read BGCF configuration data: %s\nError: %s", bgcf_str.c_str(), rapidjson::GetParseError_En(doc.GetParseError())); CL_SPROUT_BGCF_FILE_INVALID.log(); return; } try { std::map<std::string, std::vector<std::string>> new_domain_routes; std::map<std::string, std::vector<std::string>> new_number_routes; JSON_ASSERT_CONTAINS(doc, "routes"); JSON_ASSERT_ARRAY(doc["routes"]); const rapidjson::Value& routes_arr = doc["routes"]; for (rapidjson::Value::ConstValueIterator routes_it = routes_arr.Begin(); routes_it != routes_arr.End(); ++routes_it) { // An entry is valid if it has either a domain (string) OR a // number (string) AND an array of routes if ((((((*routes_it).HasMember("domain")) && ((*routes_it)["domain"].IsString())) && (!(*routes_it).HasMember("number"))) || ((!(*routes_it).HasMember("domain")) && (((*routes_it).HasMember("number")) && ((*routes_it)["number"].IsString())))) && ((*routes_it).HasMember("route") && (*routes_it)["route"].IsArray())) { std::vector<std::string> route_vec; const rapidjson::Value& route_arr = (*routes_it)["route"]; for (rapidjson::Value::ConstValueIterator route_it = route_arr.Begin(); route_it != route_arr.End(); ++route_it) { std::string route_uri = (*route_it).GetString(); TRC_DEBUG(" %s", route_uri.c_str()); route_vec.push_back(route_uri); } std::string routing_value; if ((*routes_it).HasMember("domain")) { routing_value = (*routes_it)["domain"].GetString(); new_domain_routes.insert(std::make_pair(routing_value, route_vec)); } else { routing_value = (*routes_it)["number"].GetString(); new_number_routes.insert( std::make_pair(PJUtils::remove_visual_separators(routing_value), route_vec)); } route_vec.clear(); TRC_DEBUG("Add route for %s", routing_value.c_str()); } else { TRC_WARNING("Badly formed BGCF route entry"); CL_SPROUT_BGCF_FILE_INVALID.log(); } } // Take a write lock on the mutex in RAII style boost::lock_guard<boost::shared_mutex> write_lock(_routes_rw_lock); _domain_routes = new_domain_routes; _number_routes = new_number_routes; } catch (JsonFormatError err) { TRC_ERROR("Badly formed BGCF configuration file - missing routes object"); CL_SPROUT_BGCF_FILE_INVALID.log(); } }
void JSONEnumService::update_enum() { // Check whether the file exists. struct stat s; if ((stat(_configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No ENUM configuration (file %s does not exist)", _configuration.c_str()); CL_SPROUT_ENUM_FILE_MISSING.log(_configuration.c_str()); return; } TRC_STATUS("Loading ENUM configuration from %s", _configuration.c_str()); // Read from the file std::ifstream fs(_configuration.c_str()); std::string enum_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (enum_str == "") { // LCOV_EXCL_START TRC_ERROR("Failed to read ENUM configuration data from %s", _configuration.c_str()); CL_SPROUT_ENUM_FILE_EMPTY.log(_configuration.c_str()); return; // LCOV_EXCL_STOP } // Now parse the document rapidjson::Document doc; doc.Parse<0>(enum_str.c_str()); if (doc.HasParseError()) { TRC_ERROR("Failed to read ENUM configuration data: %s\nError: %s", enum_str.c_str(), rapidjson::GetParseError_En(doc.GetParseError())); CL_SPROUT_ENUM_FILE_INVALID.log(_configuration.c_str()); return; } try { std::vector<NumberPrefix> new_number_prefixes; std::map<std::string, NumberPrefix> new_prefix_regex_map; JSON_ASSERT_CONTAINS(doc, "number_blocks"); JSON_ASSERT_ARRAY(doc["number_blocks"]); const rapidjson::Value& nb_arr = doc["number_blocks"]; for (rapidjson::Value::ConstValueIterator nb_it = nb_arr.Begin(); nb_it != nb_arr.End(); ++nb_it) { try { std::string prefix; JSON_GET_STRING_MEMBER(*nb_it, "prefix", prefix); std::string regex; JSON_GET_STRING_MEMBER(*nb_it, "regex", regex); // Entry is well-formed, so strip off visual separators and add it. TRC_DEBUG("Found valid number prefix block %s", prefix.c_str()); NumberPrefix pfix; prefix = PJUtils::remove_visual_separators(prefix); pfix.prefix = prefix; if (parse_regex_replace(regex, pfix.match, pfix.replace)) { // Create an array in order of entries in json file, and a map // (automatically sorted in order of key length) so we can later // match numbers to the most specific prefixes new_number_prefixes.push_back(pfix); new_prefix_regex_map.insert(std::make_pair(prefix, pfix)); TRC_STATUS(" Adding number prefix %s, regex=%s", pfix.prefix.c_str(), regex.c_str()); } else { TRC_WARNING("Badly formed regular expression in ENUM number block %s", regex.c_str()); } } catch (JsonFormatError err) { // Badly formed number block. TRC_WARNING("Badly formed ENUM number block (hit error at %s:%d)", err._file, err._line); CL_SPROUT_ENUM_FILE_INVALID.log(_configuration.c_str()); } } // Take a write lock on the mutex in RAII style boost::lock_guard<boost::shared_mutex> write_lock(_number_prefixes_rw_lock); _number_prefixes = new_number_prefixes; _prefix_regex_map = new_prefix_regex_map; } catch (JsonFormatError err) { TRC_ERROR("Badly formed ENUM configuration data - missing number_blocks object"); CL_SPROUT_ENUM_FILE_INVALID.log(_configuration.c_str()); } }
void SasService::extract_config() { // Check whether the file exists. struct stat s; TRC_DEBUG("stat(%s) returns %d", _configuration.c_str(), stat(_configuration.c_str(), &s)); if ((stat(_configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No SAS configuration (file %s does not exist)", _configuration.c_str()); CL_SAS_FILE_MISSING.log(); return; } TRC_STATUS("Loading SAS configuration from %s", _configuration.c_str()); // Read from the file std::ifstream fs(_configuration.c_str()); std::string sas_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (sas_str == "") { TRC_ERROR("Failed to read SAS configuration data from %s", _configuration.c_str()); CL_SAS_FILE_EMPTY.log(); return; } // Now parse the document rapidjson::Document doc; doc.Parse<0>(sas_str.c_str()); if (doc.HasParseError()) { TRC_ERROR("Failed to read SAS configuration data: %s\nError: %s", sas_str.c_str(), rapidjson::GetParseError_En(doc.GetParseError())); CL_SAS_FILE_INVALID.log(); return; } try { JSON_ASSERT_CONTAINS(doc, "sas_servers"); JSON_ASSERT_ARRAY(doc["sas_servers"]); rapidjson::Value& sas_servers = doc["sas_servers"]; for (rapidjson::Value::ValueIterator sas_it = sas_servers.Begin(); sas_it != sas_servers.End(); ++sas_it) { JSON_ASSERT_OBJECT(*sas_it); JSON_ASSERT_CONTAINS(*sas_it, "ip"); JSON_ASSERT_STRING((*sas_it)["ip"]); boost::lock_guard<boost::shared_mutex> write_lock(_sas_server_lock); _single_sas_server = (*sas_it)["ip"].GetString(); } // We have a valid rapidjson object. Write this to the _sas_servers member rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); sas_servers.Accept(writer); TRC_DEBUG("New _sas_servers config: %s", buffer.GetString()); boost::lock_guard<boost::shared_mutex> write_lock(_sas_server_lock); _sas_servers = buffer.GetString(); } catch (JsonFormatError err) { TRC_ERROR("Badly formed SAS configuration file"); CL_SAS_FILE_INVALID.log(); } }
int main(int argc, char**argv) { // Set up our exception signal handler for asserts and segfaults. signal(SIGABRT, signal_handler); signal(SIGSEGV, signal_handler); sem_init(&term_sem, 0, 0); signal(SIGTERM, terminate_handler); AstaireResolver* astaire_resolver = NULL; struct options options; options.local_host = "127.0.0.1"; options.http_address = "0.0.0.0"; options.http_port = 11888; options.http_threads = 1; options.http_worker_threads = 50; options.homestead_http_name = "homestead-http-name.unknown"; options.digest_timeout = 300; options.home_domain = "home.domain"; options.sas_system_name = ""; options.access_log_enabled = false; options.log_to_file = false; options.log_level = 0; options.astaire = ""; options.cassandra = ""; options.memcached_write_format = MemcachedWriteFormat::JSON; options.target_latency_us = 100000; options.max_tokens = 1000; options.init_token_rate = 100.0; options.min_token_rate = 10.0; options.min_token_rate = 0.0; options.exception_max_ttl = 600; options.astaire_blacklist_duration = AstaireResolver::DEFAULT_BLACKLIST_DURATION; options.http_blacklist_duration = HttpResolver::DEFAULT_BLACKLIST_DURATION; options.pidfile = ""; options.daemon = false; if (init_logging_options(argc, argv, options) != 0) { return 1; } Utils::daemon_log_setup(argc, argv, options.daemon, options.log_directory, options.log_level, options.log_to_file); std::stringstream options_ss; for (int ii = 0; ii < argc; ii++) { options_ss << argv[ii]; options_ss << " "; } std::string options_str = "Command-line options were: " + options_ss.str(); TRC_INFO(options_str.c_str()); if (init_options(argc, argv, options) != 0) { return 1; } if (options.pidfile != "") { int rc = Utils::lock_and_write_pidfile(options.pidfile); if (rc == -1) { // Failure to acquire pidfile lock TRC_ERROR("Could not write pidfile - exiting"); return 2; } } start_signal_handlers(); AccessLogger* access_logger = NULL; if (options.access_log_enabled) { TRC_STATUS("Access logging enabled to %s", options.access_log_directory.c_str()); access_logger = new AccessLogger(options.access_log_directory); } HealthChecker* hc = new HealthChecker(); hc->start_thread(); // Create an exception handler. The exception handler doesn't need // to quiesce the process before killing it. exception_handler = new ExceptionHandler(options.exception_max_ttl, false, hc); // Initialise the SasService, to read the SAS config to pass into SAS::Init SasService* sas_service = new SasService(options.sas_system_name, "memento", false); // Ensure our random numbers are unpredictable. unsigned int seed; seed = time(NULL) ^ getpid(); srand(seed); // Create a DNS resolver. int af = AF_INET; struct in6_addr dummy_addr; if (inet_pton(AF_INET6, options.local_host.c_str(), &dummy_addr) == 1) { TRC_DEBUG("Local host is an IPv6 address"); af = AF_INET6; } DnsCachedResolver* dns_resolver = new DnsCachedResolver("127.0.0.1"); // Create alarm and communication monitor objects for the conditions // reported by memento. AlarmManager* alarm_manager = new AlarmManager(); CommunicationMonitor* astaire_comm_monitor = new CommunicationMonitor(new Alarm(alarm_manager, "memento", AlarmDef::MEMENTO_ASTAIRE_COMM_ERROR, AlarmDef::CRITICAL), "Memento", "Astaire"); CommunicationMonitor* hs_comm_monitor = new CommunicationMonitor(new Alarm(alarm_manager, "memento", AlarmDef::MEMENTO_HOMESTEAD_COMM_ERROR, AlarmDef::CRITICAL), "Memento", "Homestead"); CommunicationMonitor* cass_comm_monitor = new CommunicationMonitor(new Alarm(alarm_manager, "memento", AlarmDef::MEMENTO_CASSANDRA_COMM_ERROR, AlarmDef::CRITICAL), "Memento", "Cassandra"); astaire_resolver = new AstaireResolver(dns_resolver, af, options.astaire_blacklist_duration); // Default the astaire hostname to the loopback IP if (options.astaire == "") { if (af == AF_INET6) { options.astaire = "[::1]"; } else { options.astaire = "127.0.0.1"; } } memcached_store = (Store*)new TopologyNeutralMemcachedStore(options.astaire, astaire_resolver, false, astaire_comm_monitor); AuthStore::SerializerDeserializer* serializer; std::vector<AuthStore::SerializerDeserializer*> deserializers; if (options.memcached_write_format == MemcachedWriteFormat::JSON) { serializer = new AuthStore::JsonSerializerDeserializer(); } else { serializer = new AuthStore::BinarySerializerDeserializer(); } deserializers.push_back(new AuthStore::JsonSerializerDeserializer()); deserializers.push_back(new AuthStore::BinarySerializerDeserializer()); AuthStore* auth_store = new AuthStore(memcached_store, serializer, deserializers, options.digest_timeout); LoadMonitor* load_monitor = new LoadMonitor(options.target_latency_us, options.max_tokens, options.init_token_rate, options.min_token_rate, options.max_token_rate); LastValueCache* stats_aggregator = new MementoLVC(); // Create a HTTP specific resolver. HttpResolver* http_resolver = new HttpResolver(dns_resolver, af, options.http_blacklist_duration); HttpClient* http_client = new HttpClient(false, http_resolver, nullptr, load_monitor, SASEvent::HttpLogLevel::PROTOCOL, hs_comm_monitor); HttpConnection* http_connection = new HttpConnection(options.homestead_http_name, http_client); HomesteadConnection* homestead_conn = new HomesteadConnection(http_connection); // Default to a 30s blacklist/graylist duration and port 9160 CassandraResolver* cass_resolver = new CassandraResolver(dns_resolver, af, 30, 30, 9160); // Default the cassandra hostname to the loopback IP if (options.cassandra == "") { if (af == AF_INET6) { options.cassandra = "[::1]"; } else { options.cassandra = "127.0.0.1"; } } // Create and start the call list store. CallListStore::Store* call_list_store = new CallListStore::Store(); call_list_store->configure_connection(options.cassandra, 9160, cass_comm_monitor, cass_resolver); // Test Cassandra connectivity. CassandraStore::ResultCode store_rc = call_list_store->connection_test(); if (store_rc == CassandraStore::OK) { // Store can connect to Cassandra, so start it. store_rc = call_list_store->start(); } if (store_rc != CassandraStore::OK) { TRC_ERROR("Unable to create call list store (RC = %d)", store_rc); exit(3); } HttpStackUtils::SimpleStatsManager stats_manager(stats_aggregator); HttpStack* http_stack = new HttpStack(options.http_threads, exception_handler, access_logger, load_monitor, &stats_manager); CallListTask::Config call_list_config(auth_store, homestead_conn, call_list_store, options.home_domain, stats_aggregator, hc, options.api_key); MementoSasLogger sas_logger; HttpStackUtils::PingHandler ping_handler; HttpStackUtils::SpawningHandler<CallListTask, CallListTask::Config> call_list_handler(&call_list_config, &sas_logger); HttpStackUtils::HandlerThreadPool pool(options.http_worker_threads, exception_handler); try { http_stack->initialize(); http_stack->bind_tcp_socket(options.http_address, options.http_port); http_stack->register_handler("^/ping$", &ping_handler); http_stack->register_handler("^/org.projectclearwater.call-list/users/[^/]*/call-list.xml$", pool.wrap(&call_list_handler)); http_stack->start(); } catch (HttpStack::Exception& e) { TRC_ERROR("Failed to initialize HttpStack stack - function %s, rc %d", e._func, e._rc); exit(2); } TRC_STATUS("Start-up complete - wait for termination signal"); sem_wait(&term_sem); TRC_STATUS("Termination signal received - terminating"); try { http_stack->stop(); http_stack->wait_stopped(); } catch (HttpStack::Exception& e) { TRC_ERROR("Failed to stop HttpStack stack - function %s, rc %d", e._func, e._rc); } call_list_store->stop(); call_list_store->wait_stopped(); hc->stop_thread(); delete homestead_conn; homestead_conn = NULL; delete http_connection; http_connection = NULL; delete http_client; http_client = NULL; delete call_list_store; call_list_store = NULL; delete http_resolver; http_resolver = NULL; delete cass_resolver; cass_resolver = NULL; delete dns_resolver; dns_resolver = NULL; delete load_monitor; load_monitor = NULL; delete auth_store; auth_store = NULL; delete call_list_store; call_list_store = NULL; delete astaire_resolver; astaire_resolver = NULL; delete memcached_store; memcached_store = NULL; delete exception_handler; exception_handler = NULL; delete hc; hc = NULL; delete http_stack; http_stack = NULL; delete astaire_comm_monitor; astaire_comm_monitor = NULL; delete hs_comm_monitor; hs_comm_monitor = NULL; delete cass_comm_monitor; cass_comm_monitor = NULL; delete alarm_manager; alarm_manager = NULL; delete sas_service; sas_service = NULL; signal(SIGTERM, SIG_DFL); sem_destroy(&term_sem); }
int main(int argc, char**argv) { CommunicationMonitor* hss_comm_monitor = NULL; CommunicationMonitor* cassandra_comm_monitor = NULL; // Set up our exception signal handler for asserts and segfaults. signal(SIGABRT, signal_handler); signal(SIGSEGV, signal_handler); sem_init(&term_sem, 0, 0); signal(SIGTERM, terminate_handler); struct options options; options.local_host = "127.0.0.1"; options.home_domain = "dest-realm.unknown"; options.diameter_conf = "homestead.conf"; options.dns_servers.push_back("127.0.0.1"); options.http_address = "0.0.0.0"; options.http_port = 8888; options.http_threads = 1; options.cache_threads = 10; options.cassandra = "localhost"; options.dest_realm = ""; options.dest_host = "dest-host.unknown"; options.max_peers = 2; options.server_name = "sip:server-name.unknown"; options.scheme_unknown = "Unknown"; options.scheme_digest = "SIP Digest"; options.scheme_aka = "Digest-AKAv1-MD5"; options.access_log_enabled = false; options.impu_cache_ttl = 0; options.hss_reregistration_time = 1800; options.sprout_http_name = "sprout-http-name.unknown"; options.log_to_file = false; options.log_level = 0; options.sas_server = "0.0.0.0"; options.sas_system_name = ""; options.diameter_timeout_ms = 200; options.alarms_enabled = false; options.target_latency_us = 100000; options.max_tokens = 20; options.init_token_rate = 100.0; options.min_token_rate = 10.0; options.exception_max_ttl = 600; options.http_blacklist_duration = HttpResolver::DEFAULT_BLACKLIST_DURATION; options.diameter_blacklist_duration = DiameterResolver::DEFAULT_BLACKLIST_DURATION; boost::filesystem::path p = argv[0]; // Copy the filename to a string so that we can be sure of its lifespan - // the value passed to openlog must be valid for the duration of the program. std::string filename = p.filename().c_str(); openlog(filename.c_str(), PDLOG_PID, PDLOG_LOCAL6); CL_HOMESTEAD_STARTED.log(); if (init_logging_options(argc, argv, options) != 0) { closelog(); return 1; } Log::setLoggingLevel(options.log_level); if ((options.log_to_file) && (options.log_directory != "")) { // Work out the program name from argv[0], stripping anything before the final slash. char* prog_name = argv[0]; char* slash_ptr = rindex(argv[0], '/'); if (slash_ptr != NULL) { prog_name = slash_ptr + 1; } Log::setLogger(new Logger(options.log_directory, prog_name)); } TRC_STATUS("Log level set to %d", options.log_level); std::stringstream options_ss; for (int ii = 0; ii < argc; ii++) { options_ss << argv[ii]; options_ss << " "; } std::string options_str = "Command-line options were: " + options_ss.str(); TRC_INFO(options_str.c_str()); if (init_options(argc, argv, options) != 0) { closelog(); return 1; } AccessLogger* access_logger = NULL; if (options.access_log_enabled) { TRC_STATUS("Access logging enabled to %s", options.access_log_directory.c_str()); access_logger = new AccessLogger(options.access_log_directory); } // Create a DNS resolver and a SIP specific resolver. int af = AF_INET; struct in6_addr dummy_addr; if (inet_pton(AF_INET6, options.local_host.c_str(), &dummy_addr) == 1) { TRC_DEBUG("Local host is an IPv6 address"); af = AF_INET6; } SAS::init(options.sas_system_name, "homestead", SASEvent::CURRENT_RESOURCE_BUNDLE, options.sas_server, sas_write); // Set up the statistics (Homestead specific and Diameter) const static std::string known_stats[] = { "H_latency_us", "H_hss_latency_us", "H_hss_digest_latency_us", "H_hss_subscription_latency_us", "H_cache_latency_us", "H_incoming_requests", "H_rejected_overload", "H_diameter_invalid_dest_host", "H_diameter_invalid_dest_realm", }; const static int num_known_stats = sizeof(known_stats) / sizeof(std::string); LastValueCache* lvc = new LastValueCache(num_known_stats, known_stats, "homestead", 1000); StatisticsManager* stats_manager = new StatisticsManager(lvc); StatisticCounter* realm_counter = new StatisticCounter("H_diameter_invalid_dest_realm", lvc); StatisticCounter* host_counter = new StatisticCounter("H_diameter_invalid_dest_host", lvc); if (options.alarms_enabled) { // Create Homesteads's alarm objects. Note that the alarm identifier strings must match those // in the alarm definition JSON file exactly. hss_comm_monitor = new CommunicationMonitor(new Alarm("homestead", AlarmDef::HOMESTEAD_HSS_COMM_ERROR, AlarmDef::CRITICAL)); cassandra_comm_monitor = new CommunicationMonitor(new Alarm("homestead", AlarmDef::HOMESTEAD_CASSANDRA_COMM_ERROR, AlarmDef::CRITICAL)); // Start the alarm request agent AlarmReqAgent::get_instance().start(); AlarmState::clear_all("homestead"); } // Create an exception handler. The exception handler doesn't need // to quiesce the process before killing it. HealthChecker* hc = new HealthChecker(); pthread_t health_check_thread; pthread_create(&health_check_thread, NULL, &HealthChecker::static_main_thread_function, (void*)hc); exception_handler = new ExceptionHandler(options.exception_max_ttl, false, hc); LoadMonitor* load_monitor = new LoadMonitor(options.target_latency_us, options.max_tokens, options.init_token_rate, options.min_token_rate); DnsCachedResolver* dns_resolver = new DnsCachedResolver(options.dns_servers); HttpResolver* http_resolver = new HttpResolver(dns_resolver, af, options.http_blacklist_duration); Cache* cache = Cache::get_instance(); cache->configure_connection(options.cassandra, 9160, cassandra_comm_monitor); cache->configure_workers(exception_handler, options.cache_threads, 0); // Test the connection to Cassandra before starting the store. CassandraStore::ResultCode rc = cache->connection_test(); if (rc == CassandraStore::OK) { // Cassandra connection is good, so start the store. rc = cache->start(); } if (rc != CassandraStore::OK) { CL_HOMESTEAD_CASSANDRA_CACHE_INIT_FAIL.log(rc); closelog(); TRC_ERROR("Failed to initialize the Cassandra cache with error code %d.", rc); TRC_STATUS("Homestead is shutting down"); exit(2); } HttpConnection* http = new HttpConnection(options.sprout_http_name, false, http_resolver, SASEvent::HttpLogLevel::PROTOCOL, NULL); SproutConnection* sprout_conn = new SproutConnection(http); RegistrationTerminationTask::Config* rtr_config = NULL; PushProfileTask::Config* ppr_config = NULL; Diameter::SpawningHandler<RegistrationTerminationTask, RegistrationTerminationTask::Config>* rtr_task = NULL; Diameter::SpawningHandler<PushProfileTask, PushProfileTask::Config>* ppr_task = NULL; Cx::Dictionary* dict = NULL; Diameter::Stack* diameter_stack = Diameter::Stack::get_instance(); try { diameter_stack->initialize(); diameter_stack->configure(options.diameter_conf, exception_handler, hss_comm_monitor, realm_counter, host_counter); dict = new Cx::Dictionary(); rtr_config = new RegistrationTerminationTask::Config(cache, dict, sprout_conn, options.hss_reregistration_time); ppr_config = new PushProfileTask::Config(cache, dict, options.impu_cache_ttl, options.hss_reregistration_time); rtr_task = new Diameter::SpawningHandler<RegistrationTerminationTask, RegistrationTerminationTask::Config>(dict, rtr_config); ppr_task = new Diameter::SpawningHandler<PushProfileTask, PushProfileTask::Config>(dict, ppr_config); diameter_stack->advertize_application(Diameter::Dictionary::Application::AUTH, dict->TGPP, dict->CX); diameter_stack->register_handler(dict->CX, dict->REGISTRATION_TERMINATION_REQUEST, rtr_task); diameter_stack->register_handler(dict->CX, dict->PUSH_PROFILE_REQUEST, ppr_task); diameter_stack->register_fallback_handler(dict->CX); diameter_stack->start(); } catch (Diameter::Stack::Exception& e) { CL_HOMESTEAD_DIAMETER_INIT_FAIL.log(e._func, e._rc); closelog(); TRC_ERROR("Failed to initialize Diameter stack - function %s, rc %d", e._func, e._rc); TRC_STATUS("Homestead is shutting down"); exit(2); } HttpStack* http_stack = HttpStack::get_instance(); HssCacheTask::configure_diameter(diameter_stack, options.dest_realm.empty() ? options.home_domain : options.dest_realm, options.dest_host == "0.0.0.0" ? "" : options.dest_host, options.server_name, dict); HssCacheTask::configure_cache(cache); HssCacheTask::configure_health_checker(hc); HssCacheTask::configure_stats(stats_manager); // We should only query the cache for AV information if there is no HSS. If there is an HSS, we // should always hit it. If there is not, the AV information must have been provisioned in the // "cache" (which becomes persistent). bool hss_configured = !(options.dest_realm.empty() && (options.dest_host.empty() || options.dest_host == "0.0.0.0")); ImpiTask::Config impi_handler_config(hss_configured, options.impu_cache_ttl, options.scheme_unknown, options.scheme_digest, options.scheme_aka, options.diameter_timeout_ms); ImpiRegistrationStatusTask::Config registration_status_handler_config(hss_configured, options.diameter_timeout_ms); ImpuLocationInfoTask::Config location_info_handler_config(hss_configured, options.diameter_timeout_ms); ImpuRegDataTask::Config impu_handler_config(hss_configured, options.hss_reregistration_time, options.diameter_timeout_ms); ImpuIMSSubscriptionTask::Config impu_handler_config_old(hss_configured, options.hss_reregistration_time, options.diameter_timeout_ms); HttpStackUtils::PingHandler ping_handler; HttpStackUtils::SpawningHandler<ImpiDigestTask, ImpiTask::Config> impi_digest_handler(&impi_handler_config); HttpStackUtils::SpawningHandler<ImpiAvTask, ImpiTask::Config> impi_av_handler(&impi_handler_config); HttpStackUtils::SpawningHandler<ImpiRegistrationStatusTask, ImpiRegistrationStatusTask::Config> impi_reg_status_handler(®istration_status_handler_config); HttpStackUtils::SpawningHandler<ImpuLocationInfoTask, ImpuLocationInfoTask::Config> impu_loc_info_handler(&location_info_handler_config); HttpStackUtils::SpawningHandler<ImpuRegDataTask, ImpuRegDataTask::Config> impu_reg_data_handler(&impu_handler_config); HttpStackUtils::SpawningHandler<ImpuIMSSubscriptionTask, ImpuIMSSubscriptionTask::Config> impu_ims_sub_handler(&impu_handler_config_old); try { http_stack->initialize(); http_stack->configure(options.http_address, options.http_port, options.http_threads, exception_handler, access_logger, load_monitor, stats_manager); http_stack->register_handler("^/ping$", &ping_handler); http_stack->register_handler("^/impi/[^/]*/digest$", &impi_digest_handler); http_stack->register_handler("^/impi/[^/]*/av", &impi_av_handler); http_stack->register_handler("^/impi/[^/]*/registration-status$", &impi_reg_status_handler); http_stack->register_handler("^/impu/[^/]*/location$", &impu_loc_info_handler); http_stack->register_handler("^/impu/[^/]*/reg-data$", &impu_reg_data_handler); http_stack->register_handler("^/impu/", &impu_ims_sub_handler); http_stack->start(); } catch (HttpStack::Exception& e) { CL_HOMESTEAD_HTTP_INIT_FAIL.log(e._func, e._rc); closelog(); TRC_ERROR("Failed to initialize HttpStack stack - function %s, rc %d", e._func, e._rc); TRC_STATUS("Homestead is shutting down"); exit(2); } DiameterResolver* diameter_resolver = NULL; RealmManager* realm_manager = NULL; if (hss_configured) { diameter_resolver = new DiameterResolver(dns_resolver, af, options.diameter_blacklist_duration); realm_manager = new RealmManager(diameter_stack, options.dest_realm, options.dest_host, options.max_peers, diameter_resolver); realm_manager->start(); } TRC_STATUS("Start-up complete - wait for termination signal"); sem_wait(&term_sem); TRC_STATUS("Termination signal received - terminating"); CL_HOMESTEAD_ENDED.log(); try { http_stack->stop(); http_stack->wait_stopped(); } catch (HttpStack::Exception& e) { CL_HOMESTEAD_HTTP_STOP_FAIL.log(e._func, e._rc); TRC_ERROR("Failed to stop HttpStack stack - function %s, rc %d", e._func, e._rc); } cache->stop(); cache->wait_stopped(); try { diameter_stack->stop(); diameter_stack->wait_stopped(); } catch (Diameter::Stack::Exception& e) { CL_HOMESTEAD_DIAMETER_STOP_FAIL.log(e._func, e._rc); TRC_ERROR("Failed to stop Diameter stack - function %s, rc %d", e._func, e._rc); } delete dict; dict = NULL; delete ppr_config; ppr_config = NULL; delete rtr_config; rtr_config = NULL; delete ppr_task; ppr_task = NULL; delete rtr_task; rtr_task = NULL; delete sprout_conn; sprout_conn = NULL; if (hss_configured) { realm_manager->stop(); delete realm_manager; realm_manager = NULL; delete diameter_resolver; diameter_resolver = NULL; delete dns_resolver; dns_resolver = NULL; } delete realm_counter; realm_counter = NULL; delete host_counter; host_counter = NULL; delete stats_manager; stats_manager = NULL; delete lvc; lvc = NULL; hc->terminate(); pthread_join(health_check_thread, NULL); delete hc; hc = NULL; delete exception_handler; exception_handler = NULL; delete load_monitor; load_monitor = NULL; SAS::term(); closelog(); if (options.alarms_enabled) { // Stop the alarm request agent AlarmReqAgent::get_instance().stop(); // Delete Homestead's alarm objects delete hss_comm_monitor; delete cassandra_comm_monitor; } signal(SIGTERM, SIG_DFL); sem_destroy(&term_sem); }
JSONEnumService::JSONEnumService(std::string configuration) { // Check whether the file exists. struct stat s; if ((stat(configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No ENUM configuration (file %s does not exist)", configuration.c_str()); CL_SPROUT_ENUM_FILE_MISSING.log(configuration.c_str()); return; } TRC_STATUS("Loading ENUM configuration from %s", configuration.c_str()); // Read from the file std::ifstream fs(configuration.c_str()); std::string enum_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (enum_str == "") { // LCOV_EXCL_START TRC_ERROR("Failed to read ENUM configuration data from %s", configuration.c_str()); CL_SPROUT_ENUM_FILE_EMPTY.log(configuration.c_str()); return; // LCOV_EXCL_STOP } // Now parse the document rapidjson::Document doc; doc.Parse<0>(enum_str.c_str()); if (doc.HasParseError()) { TRC_ERROR("Failed to read ENUM configuration data: %s\nError: %s", enum_str.c_str(), rapidjson::GetParseError_En(doc.GetParseError())); CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str()); return; } try { JSON_ASSERT_CONTAINS(doc, "number_blocks"); JSON_ASSERT_ARRAY(doc["number_blocks"]); const rapidjson::Value& nb_arr = doc["number_blocks"]; for (rapidjson::Value::ConstValueIterator nb_it = nb_arr.Begin(); nb_it != nb_arr.End(); ++nb_it) { try { std::string prefix; JSON_GET_STRING_MEMBER(*nb_it, "prefix", prefix); std::string regex; JSON_GET_STRING_MEMBER(*nb_it, "regex", regex); // Entry is well-formed, so add it. TRC_DEBUG("Found valid number prefix block %s", prefix.c_str()); NumberPrefix *pfix = new NumberPrefix; pfix->prefix = prefix; if (parse_regex_replace(regex, pfix->match, pfix->replace)) { _number_prefixes.push_back(pfix); TRC_STATUS(" Adding number prefix %s, regex=%s", pfix->prefix.c_str(), regex.c_str()); } else { TRC_WARNING("Badly formed regular expression in ENUM number block %s", regex.c_str()); delete pfix; } } catch (JsonFormatError err) { // Badly formed number block. TRC_WARNING("Badly formed ENUM number block (hit error at %s:%d)", err._file, err._line); CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str()); } } } catch (JsonFormatError err) { TRC_ERROR("Badly formed ENUM configuration data - missing number_blocks object"); CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str()); } }
bool MemcachedConfigFileReader::read_config(MemcachedConfig& config) { bool seen_servers = false; config.servers.clear(); config.new_servers.clear(); config.tombstone_lifetime = DEFAULT_TOMBSTONE_LIFETIME; config.filename = _filename; std::ifstream f(_filename); if (f.is_open() && f.good()) { TRC_STATUS("Reloading memcached configuration from '%s'", _filename.c_str()); while (f.good()) { std::string line; getline(f, line); line = Utils::strip_whitespace(line); TRC_DEBUG("Got line: %s", line.c_str()); if ((line.length() > 0) && (line[0] != '#')) { // Read a non-blank line. std::vector<std::string> tokens; Utils::split_string(line, '=', tokens, 0, true); std::string key; std::string value; if (tokens.size() == 1) { key = tokens[0]; value = ""; } else if (tokens.size() == 2) { key = tokens[0]; value = tokens[1]; } else { TRC_ERROR("Malformed config file (got bad line: '%s')", line.c_str()); return false; } TRC_STATUS(" %s=%s", key.c_str(), value.c_str()); if (key == "servers") { // Found line defining servers. Utils::split_string(value, ',', config.servers, 0, true); seen_servers = true; } else if (key == "new_servers") { // Found line defining new servers. Utils::split_string(value, ',', config.new_servers, 0, true); } else { TRC_ERROR("Malformed config file (got bad line: '%s')", line.c_str()); return false; } } } } else { TRC_ERROR("Failed to open '%s'", _filename.c_str()); return false; } return seen_servers; }