]> granicus.if.org Git - icinga2/blob - lib/remote/apilistener.cpp
Error Messages: Catch and log all Socket class exceptions.
[icinga2] / lib / remote / apilistener.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "remote/apilistener.hpp"
21 #include "remote/apiclient.hpp"
22 #include "remote/endpoint.hpp"
23 #include "base/convert.hpp"
24 #include "base/netstring.hpp"
25 #include "base/dynamictype.hpp"
26 #include "base/logger_fwd.hpp"
27 #include "base/objectlock.hpp"
28 #include "base/stdiostream.hpp"
29 #include "base/application.hpp"
30 #include "base/context.hpp"
31 #include "base/statsfunction.hpp"
32 #include <fstream>
33
34 using namespace icinga;
35
36 REGISTER_TYPE(ApiListener);
37
38 boost::signals2::signal<void(bool)> ApiListener::OnMasterChanged;
39
40 REGISTER_STATSFUNCTION(ApiListenerStats, &ApiListener::StatsFunc);
41
42 void ApiListener::OnConfigLoaded(void)
43 {
44         /* set up SSL context */
45         shared_ptr<X509> cert = make_shared<X509>();
46         try {
47                 cert = GetX509Certificate(GetCertPath());
48         } catch (std::exception&) {
49                 Log(LogCritical, "ApiListener", "Cannot get certificate from cert path: '" + GetCertPath() + "'.");
50                 return;
51         }
52
53         try {
54                 SetIdentity(GetCertificateCN(cert));
55         } catch (std::exception&) {
56                 Log(LogCritical, "ApiListener", "Cannot get certificate common name from cert path: '" + GetCertPath() + "'.");
57                 return;
58         }
59
60         Log(LogInformation, "ApiListener", "My API identity: " + GetIdentity());
61
62         try {
63                 m_SSLContext = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath());
64         } catch (std::exception&) {
65                 Log(LogCritical, "ApiListener", "Cannot make SSL context for cert path: '" + GetCertPath() + "' key path: '" + GetKeyPath() + "' ca path: '" + GetCaPath() + "'.");
66                 return;
67         }
68
69         if (!GetCrlPath().IsEmpty()) {
70                 try {
71                         AddCRLToSSLContext(m_SSLContext, GetCrlPath());
72                 } catch (std::exception&) {
73                         Log(LogCritical, "ApiListener", "Cannot add certificate revocation list to SSL context for crl path: '" + GetCrlPath() + "'.");
74                         return;
75                 }
76         }
77
78         if (!Endpoint::GetByName(GetIdentity())) {
79                 Log(LogCritical, "ApiListener", "Endpoint object for '" + GetIdentity() + "' is missing.");
80                 return;
81         }
82
83         SyncZoneDirs();
84 }
85
86 /**
87  * Starts the component.
88  */
89 void ApiListener::Start(void)
90 {
91         if (std::distance(DynamicType::GetObjects<ApiListener>().first, DynamicType::GetObjects<ApiListener>().second) > 1) {
92                 Log(LogCritical, "ApiListener", "Only one ApiListener object is allowed.");
93                 return;
94         }
95
96         DynamicObject::Start();
97
98         {
99                 boost::mutex::scoped_lock(m_LogLock);
100                 RotateLogFile();
101                 OpenLogFile();
102         }
103
104         /* create the primary JSON-RPC listener */
105         AddListener(GetBindPort());
106
107         m_Timer = make_shared<Timer>();
108         m_Timer->OnTimerExpired.connect(boost::bind(&ApiListener::ApiTimerHandler, this));
109         m_Timer->SetInterval(5);
110         m_Timer->Start();
111         m_Timer->Reschedule(0);
112
113         OnMasterChanged(true);
114 }
115
116 ApiListener::Ptr ApiListener::GetInstance(void)
117 {
118         BOOST_FOREACH(const ApiListener::Ptr& listener, DynamicType::GetObjects<ApiListener>())
119                 return listener;
120
121         return ApiListener::Ptr();
122 }
123
124 shared_ptr<SSL_CTX> ApiListener::GetSSLContext(void) const
125 {
126         return m_SSLContext;
127 }
128
129 Endpoint::Ptr ApiListener::GetMaster(void) const
130 {
131         Zone::Ptr zone = Zone::GetLocalZone();
132         std::vector<String> names;
133
134         BOOST_FOREACH(const Endpoint::Ptr& endpoint, zone->GetEndpoints())
135                 if (endpoint->IsConnected() || endpoint->GetName() == GetIdentity())
136                         names.push_back(endpoint->GetName());
137
138         std::sort(names.begin(), names.end());
139
140         return Endpoint::GetByName(*names.begin());
141 }
142
143 bool ApiListener::IsMaster(void) const
144 {
145         return GetMaster()->GetName() == GetIdentity();
146 }
147
148 /**
149  * Creates a new JSON-RPC listener on the specified port.
150  *
151  * @param service The port to listen on.
152  */
153 void ApiListener::AddListener(const String& service)
154 {
155         ObjectLock olock(this);
156
157         shared_ptr<SSL_CTX> sslContext = m_SSLContext;
158
159         if (!sslContext) {
160                 Log(LogCritical, "ApiListener", "SSL context is required for AddListener()");
161                 return;
162         }
163
164         std::ostringstream s;
165         s << "Adding new listener: port " << service;
166         Log(LogInformation, "ApiListener", s.str());
167
168         TcpSocket::Ptr server = make_shared<TcpSocket>();
169
170         try {
171                 server->Bind(service, AF_UNSPEC);
172         } catch(std::exception&) {
173                 Log(LogCritical, "ApiListener", "Cannot bind tcp socket on '" + service + "'.");
174                 return;
175         }
176
177         boost::thread thread(boost::bind(&ApiListener::ListenerThreadProc, this, server));
178         thread.detach();
179
180         m_Servers.insert(server);
181 }
182
183 void ApiListener::ListenerThreadProc(const Socket::Ptr& server)
184 {
185         Utility::SetThreadName("API Listener");
186
187         server->Listen();
188
189         for (;;) {
190                 try {
191                         Socket::Ptr client = server->Accept();
192                         Utility::QueueAsyncCallback(boost::bind(&ApiListener::NewClientHandler, this, client, RoleServer));
193                 } catch (std::exception&) {
194                         Log(LogCritical, "ApiListener", "Cannot accept new connection.");
195                 }
196         }
197 }
198
199 /**
200  * Creates a new JSON-RPC client and connects to the specified host and port.
201  *
202  * @param node The remote host.
203  * @param service The remote port.
204  */
205 void ApiListener::AddConnection(const String& node, const String& service)
206 {
207         {
208                 ObjectLock olock(this);
209
210                 shared_ptr<SSL_CTX> sslContext = m_SSLContext;
211
212                 if (!sslContext) {
213                         Log(LogCritical, "ApiListener", "SSL context is required for AddListener()");
214                         return;
215                 }
216         }
217
218         TcpSocket::Ptr client = make_shared<TcpSocket>();
219
220         try {
221                 client->Connect(node, service);
222                 Utility::QueueAsyncCallback(boost::bind(&ApiListener::NewClientHandler, this, client, RoleClient));
223         } catch (const std::exception& ex) {
224                 std::ostringstream info, debug;
225                 info << "Cannot connect to host '" << node << "' on port '" << service << "'";
226                 debug << info.str() << std::endl << DiagnosticInformation(ex);
227                 Log(LogCritical, "ApiListener", info.str());
228                 Log(LogDebug, "ApiListener", debug.str());
229         }
230 }
231
232 /**
233  * Processes a new client connection.
234  *
235  * @param client The new client.
236  */
237 void ApiListener::NewClientHandler(const Socket::Ptr& client, ConnectionRole role)
238 {
239         CONTEXT("Handling new API client connection");
240
241         TlsStream::Ptr tlsStream;
242
243         {
244                 ObjectLock olock(this);
245                 try {
246                         tlsStream = make_shared<TlsStream>(client, role, m_SSLContext);
247                 } catch (std::exception&) {
248                         Log(LogCritical, "ApiListener", "Cannot create tls stream from client connection.");
249                         return;
250                 }
251         }
252
253         try {
254                 tlsStream->Handshake();
255         } catch (std::exception) {
256                 Log(LogCritical, "ApiListener", "Client TLS handshake failed.");
257                 return;
258         }
259
260         shared_ptr<X509> cert = tlsStream->GetPeerCertificate();
261         String identity;
262
263         try {
264                 identity = GetCertificateCN(cert);
265         } catch (std::exception&) {
266                 Log(LogCritical, "ApiListener", "Cannot get certificate common name from cert path: '" + GetCertPath() + "'.");
267                 return;
268         }
269
270         Log(LogInformation, "ApiListener", "New client connection for identity '" + identity + "'");
271
272         Endpoint::Ptr endpoint = Endpoint::GetByName(identity);
273
274         bool need_sync = false;
275
276         if (endpoint)
277                 need_sync = !endpoint->IsConnected();
278
279         ApiClient::Ptr aclient = make_shared<ApiClient>(identity, tlsStream, role);
280         aclient->Start();
281
282         if (endpoint) {
283                 if (need_sync) {
284                         {
285                                 ObjectLock olock(endpoint);
286
287                                 endpoint->SetSyncing(true);
288                         }
289
290                         ReplayLog(aclient);
291                 }
292
293                 SendConfigUpdate(aclient);
294
295                 endpoint->AddClient(aclient);
296         } else
297                 AddAnonymousClient(aclient);
298 }
299
300 void ApiListener::ApiTimerHandler(void)
301 {
302         double now = Utility::GetTime();
303
304         std::vector<int> files;
305         Utility::Glob(GetApiDir() + "log/*", boost::bind(&ApiListener::LogGlobHandler, boost::ref(files), _1), GlobFile);
306         std::sort(files.begin(), files.end());
307
308         BOOST_FOREACH(int ts, files) {
309                 bool need = false;
310
311                 BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects<Endpoint>()) {
312                         if (endpoint->GetName() == GetIdentity())
313                                 continue;
314
315                         if (endpoint->GetLogDuration() >= 0 && ts < now - endpoint->GetLogDuration())
316                                 continue;
317
318                         if (ts > endpoint->GetLocalLogPosition()) {
319                                 need = true;
320                                 break;
321                         }
322                 }
323
324                 if (!need) {
325                         String path = GetApiDir() + "log/" + Convert::ToString(ts);
326                         Log(LogNotice, "ApiListener", "Removing old log file: " + path);
327                         (void)unlink(path.CStr());
328                 }
329         }
330
331         if (IsMaster()) {
332                 Zone::Ptr my_zone = Zone::GetLocalZone();
333
334                 BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjects<Zone>()) {
335                         /* only connect to endpoints in a) the same zone b) our parent zone c) immediate child zones */
336                         if (my_zone != zone && my_zone != zone->GetParent() && zone != my_zone->GetParent())
337                                 continue;
338
339                         bool connected = false;
340
341                         BOOST_FOREACH(const Endpoint::Ptr& endpoint, zone->GetEndpoints()) {
342                                 if (endpoint->IsConnected()) {
343                                         connected = true;
344                                         break;
345                                 }
346                         }
347
348                         /* don't connect to an endpoint if we already have a connection to the zone */
349                         if (connected)
350                                 continue;
351
352                         BOOST_FOREACH(const Endpoint::Ptr& endpoint, zone->GetEndpoints()) {
353                                 /* don't connect to ourselves */
354                                 if (endpoint->GetName() == GetIdentity())
355                                         continue;
356
357                                 /* don't try to connect to endpoints which don't have a host and port */
358                                 if (endpoint->GetHost().IsEmpty() || endpoint->GetPort().IsEmpty())
359                                         continue;
360
361                                 AddConnection(endpoint->GetHost(), endpoint->GetPort());
362                         }
363                 }
364         }
365
366         BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects<Endpoint>()) {
367                 if (!endpoint->IsConnected())
368                         continue;
369
370                 double ts = endpoint->GetRemoteLogPosition();
371
372                 if (ts == 0)
373                         continue;
374
375                 Dictionary::Ptr lparams = make_shared<Dictionary>();
376                 lparams->Set("log_position", ts);
377
378                 Dictionary::Ptr lmessage = make_shared<Dictionary>();
379                 lmessage->Set("jsonrpc", "2.0");
380                 lmessage->Set("method", "log::SetLogPosition");
381                 lmessage->Set("params", lparams);
382
383                 BOOST_FOREACH(const ApiClient::Ptr& client, endpoint->GetClients())
384                         client->SendMessage(lmessage);
385
386                 Log(LogNotice, "ApiListener", "Setting log position for identity '" + endpoint->GetName() + "': " +
387                         Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", ts));
388         }
389
390         Log(LogNotice, "ApiListener", "Current zone master: " + GetMaster()->GetName());
391
392         std::vector<String> names;
393         BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects<Endpoint>())
394                 if (endpoint->IsConnected())
395                         names.push_back(endpoint->GetName() + " (" + Convert::ToString(endpoint->GetClients().size()) + ")");
396
397         Log(LogNotice, "ApiListener", "Connected endpoints: " + Utility::NaturalJoin(names));
398 }
399
400 void ApiListener::RelayMessage(const MessageOrigin& origin, const DynamicObject::Ptr& secobj, const Dictionary::Ptr& message, bool log)
401 {
402         m_RelayQueue.Enqueue(boost::bind(&ApiListener::SyncRelayMessage, this, origin, secobj, message, log));
403 }
404
405 void ApiListener::PersistMessage(const Dictionary::Ptr& message)
406 {
407         double ts = message->Get("ts");
408
409         ASSERT(ts != 0);
410
411         Dictionary::Ptr pmessage = make_shared<Dictionary>();
412         pmessage->Set("timestamp", ts);
413
414         pmessage->Set("message", JsonSerialize(message));
415
416         boost::mutex::scoped_lock lock(m_LogLock);
417         if (m_LogFile) {
418                 NetString::WriteStringToStream(m_LogFile, JsonSerialize(pmessage));
419                 m_LogMessageCount++;
420                 SetLogMessageTimestamp(ts);
421
422                 if (m_LogMessageCount > 50000) {
423                         CloseLogFile();
424                         RotateLogFile();
425                         OpenLogFile();
426                 }
427         }
428 }
429
430 void ApiListener::SyncRelayMessage(const MessageOrigin& origin, const DynamicObject::Ptr& secobj, const Dictionary::Ptr& message, bool log)
431 {
432         double ts = Utility::GetTime();
433         message->Set("ts", ts);
434
435         Log(LogNotice, "ApiListener", "Relaying '" + message->Get("method") + "' message");
436
437         if (log)
438                 m_LogQueue.Enqueue(boost::bind(&ApiListener::PersistMessage, this, message));
439
440         if (origin.FromZone)
441                 message->Set("originZone", origin.FromZone->GetName());
442
443         bool is_master = IsMaster();
444         Endpoint::Ptr master = GetMaster();
445         Zone::Ptr my_zone = Zone::GetLocalZone();
446
447         std::vector<Endpoint::Ptr> skippedEndpoints;
448         std::set<Zone::Ptr> finishedZones;
449
450         BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects<Endpoint>()) {
451                 /* don't relay messages to ourselves or disconnected endpoints */
452                 if (endpoint->GetName() == GetIdentity() || !endpoint->IsConnected())
453                         continue;
454
455                 Zone::Ptr target_zone = endpoint->GetZone();
456
457                 /* don't relay the message to the zone through more than one endpoint */
458                 if (finishedZones.find(target_zone) != finishedZones.end()) {
459                         skippedEndpoints.push_back(endpoint);
460                         continue;
461                 }
462
463                 /* don't relay messages back to the endpoint which we got the message from */
464                 if (origin.FromClient && endpoint == origin.FromClient->GetEndpoint()) {
465                         skippedEndpoints.push_back(endpoint);
466                         continue;
467                 }
468
469                 /* don't relay messages back to the zone which we got the message from */
470                 if (origin.FromZone && target_zone == origin.FromZone) {
471                         skippedEndpoints.push_back(endpoint);
472                         continue;
473                 }
474
475                 /* only relay message to the master if we're not currently the master */
476                 if (!is_master && master != endpoint) {
477                         skippedEndpoints.push_back(endpoint);
478                         continue;
479                 }
480
481                 /* only relay the message to a) the same zone, b) the parent zone and c) direct child zones */
482                 if (target_zone != my_zone && target_zone != my_zone->GetParent() &&
483                     secobj->GetZone() != target_zone->GetName()) {
484                         skippedEndpoints.push_back(endpoint);
485                         continue;
486                 }
487
488                 /* only relay messages to zones which have access to the object */
489                 if (!target_zone->CanAccessObject(secobj))
490                         continue;
491
492                 finishedZones.insert(target_zone);
493
494                 {
495                         ObjectLock olock(endpoint);
496
497                         if (!endpoint->GetSyncing()) {
498                                 Log(LogNotice, "ApiListener", "Sending message to '" + endpoint->GetName() + "'");
499
500                                 BOOST_FOREACH(const ApiClient::Ptr& client, endpoint->GetClients())
501                                         client->SendMessage(message);
502                         }
503                 }
504         }
505
506         BOOST_FOREACH(const Endpoint::Ptr& endpoint, skippedEndpoints)
507                 endpoint->SetLocalLogPosition(ts);
508 }
509
510 String ApiListener::GetApiDir(void)
511 {
512         return Application::GetLocalStateDir() + "/lib/icinga2/api/";
513 }
514
515 /* must hold m_LogLock */
516 void ApiListener::OpenLogFile(void)
517 {
518         String path = GetApiDir() + "log/current";
519
520         std::fstream *fp = new std::fstream(path.CStr(), std::fstream::out | std::ofstream::app);
521
522         if (!fp->good()) {
523                 Log(LogWarning, "ApiListener", "Could not open spool file: " + path);
524                 return;
525         }
526
527         m_LogFile = make_shared<StdioStream>(fp, true);
528         m_LogMessageCount = 0;
529         SetLogMessageTimestamp(Utility::GetTime());
530 }
531
532 /* must hold m_LogLock */
533 void ApiListener::CloseLogFile(void)
534 {
535         if (!m_LogFile)
536                 return;
537
538         m_LogFile->Close();
539         m_LogFile.reset();
540 }
541
542 /* must hold m_LogLock */
543 void ApiListener::RotateLogFile(void)
544 {
545         double ts = GetLogMessageTimestamp();
546
547         if (ts == 0)
548                 ts = Utility::GetTime();
549
550         String oldpath = GetApiDir() + "log/current";
551         String newpath = GetApiDir() + "log/" + Convert::ToString(static_cast<int>(ts)+1);
552         (void) rename(oldpath.CStr(), newpath.CStr());
553 }
554
555 void ApiListener::LogGlobHandler(std::vector<int>& files, const String& file)
556 {
557         String name = Utility::BaseName(file);
558
559         int ts;
560
561         try {
562                 ts = Convert::ToLong(name);
563         }
564         catch (const std::exception&) {
565                 return;
566         }
567
568         files.push_back(ts);
569 }
570
571 void ApiListener::ReplayLog(const ApiClient::Ptr& client)
572 {
573         Endpoint::Ptr endpoint = client->GetEndpoint();
574
575         CONTEXT("Replaying log for Endpoint '" + endpoint->GetName() + "'");
576
577         int count = -1;
578         double peer_ts = endpoint->GetLocalLogPosition();
579         bool last_sync = false;
580
581         for (;;) {
582                 boost::mutex::scoped_lock lock(m_LogLock);
583
584                 CloseLogFile();
585                 RotateLogFile();
586
587                 if (count == -1 || count > 50000) {
588                         OpenLogFile();
589                         lock.unlock();
590                 } else {
591                         last_sync = true;
592                 }
593
594                 count = 0;
595
596                 std::vector<int> files;
597                 Utility::Glob(GetApiDir() + "log/*", boost::bind(&ApiListener::LogGlobHandler, boost::ref(files), _1), GlobFile);
598                 std::sort(files.begin(), files.end());
599
600                 BOOST_FOREACH(int ts, files) {
601                         String path = GetApiDir() + "log/" + Convert::ToString(ts);
602
603                         if (ts < peer_ts)
604                                 continue;
605
606                         Log(LogNotice, "ApiListener", "Replaying log: " + path);
607
608                         std::fstream *fp = new std::fstream(path.CStr(), std::fstream::in);
609                         StdioStream::Ptr logStream = make_shared<StdioStream>(fp, true);
610
611                         String message;
612                         while (true) {
613                                 Dictionary::Ptr pmessage;
614
615                                 try {
616                                         if (!NetString::ReadStringFromStream(logStream, &message))
617                                                 break;
618
619                                         pmessage = JsonDeserialize(message);
620                                 } catch (const std::exception&) {
621                                         Log(LogWarning, "ApiListener", "Unexpected end-of-file for cluster log: " + path);
622
623                                         /* Log files may be incomplete or corrupted. This is perfectly OK. */
624                                         break;
625                                 }
626
627                                 if (pmessage->Get("timestamp") <= peer_ts)
628                                         continue;
629
630                                 NetString::WriteStringToStream(client->GetStream(), pmessage->Get("message"));
631                                 count++;
632
633                                 peer_ts = pmessage->Get("timestamp");
634                         }
635
636                         logStream->Close();
637                 }
638
639                 Log(LogNotice, "ApiListener", "Replayed " + Convert::ToString(count) + " messages.");
640
641                 if (last_sync) {
642                         {
643                                 ObjectLock olock2(endpoint);
644                                 endpoint->SetSyncing(false);
645                         }
646
647                         OpenLogFile();
648
649                         break;
650                 }
651         }
652 }
653
654 Value ApiListener::StatsFunc(Dictionary::Ptr& status, Dictionary::Ptr& perfdata)
655 {
656         Dictionary::Ptr nodes = make_shared<Dictionary>();
657         std::pair<Dictionary::Ptr, Dictionary::Ptr> stats;
658
659         ApiListener::Ptr listener = ApiListener::GetInstance();
660
661         if (!listener)
662                 return 0;
663
664         stats = listener->GetStatus();
665
666         BOOST_FOREACH(Dictionary::Pair const& kv, stats.second)
667                 perfdata->Set("api_" + kv.first, kv.second);
668
669         status->Set("api", stats.first);
670
671         return 0;
672 }
673
674 std::pair<Dictionary::Ptr, Dictionary::Ptr> ApiListener::GetStatus(void)
675 {
676         Dictionary::Ptr status = make_shared<Dictionary>();
677         Dictionary::Ptr perfdata = make_shared<Dictionary>();
678
679         /* cluster stats */
680         status->Set("identity", GetIdentity());
681
682         double count_endpoints = 0;
683         Array::Ptr not_connected_endpoints = make_shared<Array>();
684         Array::Ptr connected_endpoints = make_shared<Array>();
685
686         BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects<Endpoint>()) {
687                 if (endpoint->GetName() == GetIdentity())
688                         continue;
689
690                 count_endpoints++;
691
692                 if (!endpoint->IsConnected())
693                         not_connected_endpoints->Add(endpoint->GetName());
694                 else
695                         connected_endpoints->Add(endpoint->GetName());
696         }
697
698         status->Set("num_endpoints", count_endpoints);
699         status->Set("num_conn_endpoints", connected_endpoints->GetLength());
700         status->Set("num_not_conn_endpoints", not_connected_endpoints->GetLength());
701         status->Set("conn_endpoints", connected_endpoints);
702         status->Set("not_conn_endpoints", not_connected_endpoints);
703
704         perfdata->Set("num_endpoints", count_endpoints);
705         perfdata->Set("num_conn_endpoints", Convert::ToDouble(connected_endpoints->GetLength()));
706         perfdata->Set("num_not_conn_endpoints", Convert::ToDouble(not_connected_endpoints->GetLength()));
707
708         return std::make_pair(status, perfdata);
709 }
710
711 void ApiListener::AddAnonymousClient(const ApiClient::Ptr& aclient)
712 {
713         ObjectLock olock(this);
714         m_AnonymousClients.insert(aclient);
715 }
716
717 void ApiListener::RemoveAnonymousClient(const ApiClient::Ptr& aclient)
718 {
719         ObjectLock olock(this);
720         m_AnonymousClients.erase(aclient);
721 }
722
723 std::set<ApiClient::Ptr> ApiListener::GetAnonymousClients(void) const
724 {
725         ObjectLock olock(this);
726         return m_AnonymousClients;
727 }