]> granicus.if.org Git - icinga2/blob - lib/icinga/host.cpp
Refactor the macro resolver
[icinga2] / lib / icinga / host.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012 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 "icinga/host.h"
21 #include "icinga/service.h"
22 #include "icinga/hostgroup.h"
23 #include "base/dynamictype.h"
24 #include "base/objectlock.h"
25 #include "base/logger_fwd.h"
26 #include "base/timer.h"
27 #include "base/convert.h"
28 #include "config/configitembuilder.h"
29 #include "config/configcompilercontext.h"
30 #include <boost/tuple/tuple.hpp>
31 #include <boost/smart_ptr/make_shared.hpp>
32 #include <boost/foreach.hpp>
33
34 using namespace icinga;
35
36 static boost::mutex l_ServiceMutex;
37 static std::map<String, std::map<String, Service::WeakPtr> > l_ServicesCache;
38 static bool l_ServicesCacheNeedsUpdate = false;
39 static Timer::Ptr l_ServicesCacheTimer;
40
41 REGISTER_SCRIPTFUNCTION(ValidateServiceDictionary, &Host::ValidateServiceDictionary);
42
43 REGISTER_TYPE(Host);
44
45 Host::Host(const Dictionary::Ptr& serializedUpdate)
46         : DynamicObject(serializedUpdate)
47 {
48         RegisterAttribute("display_name", Attribute_Config, &m_DisplayName);
49         RegisterAttribute("hostgroups", Attribute_Config, &m_HostGroups);
50         RegisterAttribute("macros", Attribute_Config, &m_Macros);
51         RegisterAttribute("hostdependencies", Attribute_Config, &m_HostDependencies);
52         RegisterAttribute("servicedependencies", Attribute_Config, &m_ServiceDependencies);
53         RegisterAttribute("hostcheck", Attribute_Config, &m_HostCheck);
54
55 }
56
57 Host::~Host(void)
58 {
59         HostGroup::InvalidateMembersCache();
60
61         if (m_SlaveServices) {
62                 ConfigItem::Ptr service;
63                 BOOST_FOREACH(boost::tie(boost::tuples::ignore, service), m_SlaveServices) {
64                         service->Unregister();
65                 }
66         }
67 }
68
69 void Host::OnRegistrationCompleted(void)
70 {
71         ASSERT(!OwnsLock());
72
73         DynamicObject::OnRegistrationCompleted();
74
75         Host::InvalidateServicesCache();
76         UpdateSlaveServices();
77 }
78
79 String Host::GetDisplayName(void) const
80 {
81         if (!m_DisplayName.IsEmpty())
82                 return m_DisplayName;
83         else
84                 return GetName();
85 }
86
87 Host::Ptr Host::GetByName(const String& name)
88 {
89         DynamicObject::Ptr configObject = DynamicObject::GetObject("Host", name);
90
91         return dynamic_pointer_cast<Host>(configObject);
92 }
93
94 Array::Ptr Host::GetGroups(void) const
95 {
96         return m_HostGroups;;
97 }
98
99 Dictionary::Ptr Host::GetMacros(void) const
100 {
101         return m_Macros;
102 }
103
104 Array::Ptr Host::GetHostDependencies(void) const
105 {
106         return m_HostDependencies;;
107 }
108
109 Array::Ptr Host::GetServiceDependencies(void) const
110 {
111         return m_ServiceDependencies;
112 }
113
114 String Host::GetHostCheck(void) const
115 {
116         return m_HostCheck;
117 }
118
119 bool Host::IsReachable(void) const
120 {
121         ASSERT(!OwnsLock());
122
123         std::set<Service::Ptr> parentServices = GetParentServices();
124
125         BOOST_FOREACH(const Service::Ptr& service, parentServices) {
126                 ObjectLock olock(service);
127
128                 /* ignore pending services */
129                 if (!service->GetLastCheckResult())
130                         continue;
131
132                 /* ignore soft states */
133                 if (service->GetStateType() == StateTypeSoft)
134                         continue;
135
136                 /* ignore services states OK and Warning */
137                 if (service->GetState() == StateOK ||
138                     service->GetState() == StateWarning)
139                         continue;
140
141                 return false;
142         }
143
144         std::set<Host::Ptr> parentHosts = GetParentHosts();
145
146         BOOST_FOREACH(const Host::Ptr& host, parentHosts) {
147                 Service::Ptr hc = host->GetHostCheckService();
148
149                 /* ignore hosts that don't have a hostcheck */
150                 if (!hc)
151                         continue;
152
153                 ObjectLock olock(hc);
154
155                 /* ignore soft states */
156                 if (hc->GetStateType() == StateTypeSoft)
157                         continue;
158
159                 /* ignore hosts that are up */
160                 if (hc->GetState() == StateOK)
161                         continue;
162
163                 return false;
164         }
165
166         return true;
167 }
168
169 void Host::UpdateSlaveServices(void)
170 {
171         ASSERT(!OwnsLock());
172
173         ConfigItem::Ptr item = ConfigItem::GetObject("Host", GetName());
174
175         /* Don't create slave services unless we own this object */
176         if (!item)
177                 return;
178
179         Dictionary::Ptr oldServices = m_SlaveServices;
180         Dictionary::Ptr serviceDescs = Get("services");
181
182         Dictionary::Ptr newServices = boost::make_shared<Dictionary>();
183
184         if (serviceDescs) {
185                 ObjectLock olock(serviceDescs);
186                 String svcname;
187                 Value svcdesc;
188                 BOOST_FOREACH(boost::tie(svcname, svcdesc), serviceDescs) {
189                         if (svcdesc.IsScalar())
190                                 svcname = svcdesc;
191
192                         std::ostringstream namebuf;
193                         namebuf << GetName() << "-" << svcname;
194                         String name = namebuf.str();
195
196                         ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(item->GetDebugInfo());
197                         builder->SetType("Service");
198                         builder->SetName(name);
199                         builder->AddExpression("host_name", OperatorSet, GetName());
200                         builder->AddExpression("display_name", OperatorSet, svcname);
201                         builder->AddExpression("short_name", OperatorSet, svcname);
202
203                         if (!svcdesc.IsObjectType<Dictionary>())
204                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Service description must be either a string or a dictionary."));
205
206                         Dictionary::Ptr service = svcdesc;
207
208                         Array::Ptr templates = service->Get("templates");
209
210                         if (templates) {
211                                 ObjectLock olock(templates);
212
213                                 BOOST_FOREACH(const Value& tmpl, templates) {
214                                         builder->AddParent(tmpl);
215                                 }
216                         }
217
218                         /* Clone attributes from the host object. */
219                         std::set<String, string_iless> keys;
220                         keys.insert("check_interval");
221                         keys.insert("retry_interval");
222                         keys.insert("servicegroups");
223                         keys.insert("checkers");
224                         keys.insert("notification_interval");
225                         keys.insert("check_period");
226                         keys.insert("servicedependencies");
227                         keys.insert("hostdependencies");
228                         keys.insert("export_macros");
229
230                         ExpressionList::Ptr host_exprl = boost::make_shared<ExpressionList>();
231                         item->GetLinkedExpressionList()->ExtractFiltered(keys, host_exprl);
232                         builder->AddExpressionList(host_exprl);
233
234                         /* Clone attributes from the service expression list. */
235                         std::vector<String> path;
236                         path.push_back("services");
237                         path.push_back(svcname);
238
239                         ExpressionList::Ptr svc_exprl = boost::make_shared<ExpressionList>();
240                         item->GetLinkedExpressionList()->ExtractPath(path, svc_exprl);
241                         builder->AddExpressionList(svc_exprl);
242
243                         ConfigItem::Ptr serviceItem = builder->Compile();
244                         DynamicObject::Ptr dobj = serviceItem->Commit();
245
246                         newServices->Set(name, serviceItem);
247                 }
248         }
249
250         if (oldServices) {
251                 ObjectLock olock(oldServices);
252
253                 ConfigItem::Ptr service;
254                 BOOST_FOREACH(boost::tie(boost::tuples::ignore, service), oldServices) {
255                         if (!service)
256                                 continue;
257
258                         if (!newServices->Contains(service->GetName()))
259                                 service->Unregister();
260                 }
261         }
262
263         newServices->Seal();
264
265         Set("slave_services", newServices);
266 }
267
268 void Host::OnAttributeChanged(const String& name)
269 {
270         ASSERT(!OwnsLock());
271
272         if (name == "hostgroups")
273                 HostGroup::InvalidateMembersCache();
274         else if (name == "services") {
275                 UpdateSlaveServices();
276         } else if (name == "notifications") {
277                 BOOST_FOREACH(const Service::Ptr& service, GetServices()) {
278                         service->UpdateSlaveNotifications();
279                 }
280         }
281 }
282
283 std::set<Service::Ptr> Host::GetServices(void) const
284 {
285         std::set<Service::Ptr> services;
286
287         boost::mutex::scoped_lock lock(l_ServiceMutex);
288
289         Service::WeakPtr wservice;
290         BOOST_FOREACH(boost::tie(boost::tuples::ignore, wservice), l_ServicesCache[GetName()]) {
291                 Service::Ptr service = wservice.lock();
292
293                 if (!service)
294                         continue;
295
296                 services.insert(service);
297         }
298
299         return services;
300 }
301
302 void Host::InvalidateServicesCache(void)
303 {
304         {
305                 boost::mutex::scoped_lock lock(l_ServiceMutex);
306
307                 if (l_ServicesCacheNeedsUpdate)
308                         return; /* Someone else has already requested a refresh. */
309
310                 if (!l_ServicesCacheTimer) {
311                         l_ServicesCacheTimer = boost::make_shared<Timer>();
312                         l_ServicesCacheTimer->SetInterval(0.5);
313                         l_ServicesCacheTimer->OnTimerExpired.connect(boost::bind(&Host::RefreshServicesCache));
314                         l_ServicesCacheTimer->Start();
315                 }
316
317                 l_ServicesCacheNeedsUpdate = true;
318         }
319 }
320
321 void Host::RefreshServicesCache(void)
322 {
323         {
324                 boost::mutex::scoped_lock lock(l_ServiceMutex);
325
326                 if (!l_ServicesCacheNeedsUpdate)
327                         return;
328
329                 l_ServicesCacheNeedsUpdate = false;
330         }
331
332         Log(LogDebug, "icinga", "Updating Host services cache.");
333
334         std::map<String, std::map<String, Service::WeakPtr> > newServicesCache;
335
336         BOOST_FOREACH(const DynamicObject::Ptr& object, DynamicType::GetObjects("Service")) {
337                 const Service::Ptr& service = static_pointer_cast<Service>(object);
338
339                 Host::Ptr host = service->GetHost();
340
341                 if (!host)
342                         continue;
343
344                 // TODO: assert for duplicate short_names
345
346                 newServicesCache[host->GetName()][service->GetShortName()] = service;
347         }
348
349         boost::mutex::scoped_lock lock(l_ServiceMutex);
350         l_ServicesCache.swap(newServicesCache);
351 }
352
353 void Host::ValidateServiceDictionary(const ScriptTask::Ptr& task, const std::vector<Value>& arguments)
354 {
355         if (arguments.size() < 1)
356                 BOOST_THROW_EXCEPTION(std::invalid_argument("Missing argument: Location must be specified."));
357
358         if (arguments.size() < 2)
359                 BOOST_THROW_EXCEPTION(std::invalid_argument("Missing argument: Attribute dictionary must be specified."));
360
361         String location = arguments[0];
362         Dictionary::Ptr attrs = arguments[1];
363         ObjectLock olock(attrs);
364
365         String key;
366         Value value;
367         BOOST_FOREACH(boost::tie(key, value), attrs) {
368                 std::vector<String> templates;
369
370                 if (!value.IsObjectType<Dictionary>())
371                         BOOST_THROW_EXCEPTION(std::invalid_argument("Service description must be a dictionary."));
372
373                 Dictionary::Ptr serviceDesc = value;
374
375                 Array::Ptr templatesArray = serviceDesc->Get("templates");
376
377                 if (templatesArray) {
378                         ObjectLock tlock(templatesArray);
379
380                         BOOST_FOREACH(const Value& tmpl, templatesArray) {
381                                 templates.push_back(tmpl);
382                         }
383                 }
384
385                 BOOST_FOREACH(const String& name, templates) {
386                         ConfigItem::Ptr item;
387
388                         ConfigCompilerContext *context = ConfigCompilerContext::GetContext();
389
390                         if (context)
391                                 item = context->GetItem("Service", name);
392
393                         /* ignore already active objects while we're in the compiler
394                          * context and linking to existing items is disabled. */
395                         if (!item && (!context || (context->GetFlags() & CompilerLinkExisting)))
396                                 item = ConfigItem::GetObject("Service", name);
397
398                         if (!item) {
399                                 ConfigCompilerContext::GetContext()->AddError(false, "Validation failed for " +
400                                     location + ": Template '" + name + "' not found.");
401                         }
402                 }
403         }
404
405         task->FinishResult(Empty);
406 }
407
408 Service::Ptr Host::GetServiceByShortName(const Value& name) const
409 {
410         if (name.IsScalar()) {
411                 {
412                         boost::mutex::scoped_lock lock(l_ServiceMutex);
413
414                         std::map<String, Service::WeakPtr>& services = l_ServicesCache[GetName()];
415                         std::map<String, Service::WeakPtr>::iterator it = services.find(name);
416
417                         if (it != services.end()) {
418                                 Service::Ptr service = it->second.lock();
419                                 ASSERT(service);
420                                 return service;
421                         }
422                 }
423
424                 return Service::Ptr();
425         } else if (name.IsObjectType<Dictionary>()) {
426                 Dictionary::Ptr dict = name;
427                 String short_name;
428
429                 ASSERT(dict->IsSealed());
430
431                 return Service::GetByNamePair(dict->Get("host"), dict->Get("service"));
432         } else {
433                 BOOST_THROW_EXCEPTION(std::invalid_argument("Host/Service name pair is invalid."));
434         }
435 }
436
437 std::set<Host::Ptr> Host::GetParentHosts(void) const
438 {
439         std::set<Host::Ptr> parents;
440
441         Array::Ptr dependencies = GetHostDependencies();
442
443         if (dependencies) {
444                 ObjectLock olock(dependencies);
445
446                 BOOST_FOREACH(const Value& value, dependencies) {
447                         if (value == GetName())
448                                 continue;
449
450                         Host::Ptr host = GetByName(value);
451
452                         if (!host)
453                                 continue;
454
455                         parents.insert(host);
456                 }
457         }
458
459         return parents;
460 }
461
462 Service::Ptr Host::GetHostCheckService(void) const
463 {
464         String host_check = GetHostCheck();
465
466         if (host_check.IsEmpty())
467                 return Service::Ptr();
468
469         return GetServiceByShortName(host_check);
470 }
471
472 std::set<Service::Ptr> Host::GetParentServices(void) const
473 {
474         std::set<Service::Ptr> parents;
475
476         Array::Ptr dependencies = GetServiceDependencies();
477
478         if (dependencies) {
479                 ObjectLock olock(dependencies);
480
481                 BOOST_FOREACH(const Value& value, dependencies) {
482                         parents.insert(GetServiceByShortName(value));
483                 }
484         }
485
486         return parents;
487 }
488
489 HostState Host::CalculateState(ServiceState state, bool reachable)
490 {
491         if (!reachable)
492                 return HostUnreachable;
493
494         switch (state) {
495                 case StateOK:
496                 case StateWarning:
497                         return HostUp;
498                 default:
499                         return HostDown;
500         }
501 }
502
503 HostState Host::GetLastState(void) const
504 {
505         ASSERT(!OwnsLock());
506
507         if (!IsReachable())
508                 return HostUnreachable;
509
510         Service::Ptr hc = GetHostCheckService();
511
512         if (!hc)
513                 return HostUp;
514
515         switch (hc->GetLastState()) {
516                 case StateOK:
517                 case StateWarning:
518                         return HostUp;
519                 default:
520                         return HostDown;
521         }
522 }
523
524 StateType Host::GetLastStateType(void) const
525 {
526         Service::Ptr hc = GetHostCheckService();
527
528         if (!hc)
529                 return StateTypeHard;
530
531         return hc->GetLastStateType();
532 }
533
534 String Host::StateToString(HostState state)
535 {
536         switch (state) {
537                 case HostUp:
538                         return "UP";
539                 case HostDown:
540                         return "DOWN";
541                 case HostUnreachable:
542                         return "UNREACHABLE";
543                 default:
544                         return "INVALID";
545         }
546 }
547
548 bool Host::ResolveMacro(const String& macro, const Dictionary::Ptr& cr, String *result) const
549 {
550         if (macro == "HOSTNAME" || macro == "HOSTALIAS") {
551                 *result = GetName();
552                 return true;
553         }
554         else if (macro == "HOSTDISPLAYNAME") {
555                 *result = GetDisplayName();
556                 return true;
557         }
558
559         Service::Ptr hc = GetHostCheckService();
560         Dictionary::Ptr hccr;
561
562         if (hc) {
563                 ServiceState state = hc->GetState();
564                 bool reachable = IsReachable();
565
566                 if (macro == "HOSTSTATE") {
567                         *result = Convert::ToString(CalculateState(state, reachable));
568                         return true;
569                 } else if (macro == "HOSTSTATEID") {
570                         *result = Convert::ToString(state);
571                         return true;
572                 } else if (macro == "HOSTSTATETYPE") {
573                         *result = Service::StateTypeToString(hc->GetStateType());
574                         return true;
575                 } else if (macro == "HOSTATTEMPT") {
576                         *result = Convert::ToString(hc->GetCurrentCheckAttempt());
577                         return true;
578                 } else if (macro == "MAXHOSTATTEMPT") {
579                         *result = Convert::ToString(hc->GetMaxCheckAttempts());
580                         return true;
581                 } else if (macro == "LASTHOSTSTATE") {
582                         *result = StateToString(GetLastState());
583                         return true;
584                 } else if (macro == "LASTHOSTSTATEID") {
585                         *result = Convert::ToString(GetLastState());
586                         return true;
587                 } else if (macro == "LASTHOSTSTATETYPE") {
588                         *result = Service::StateTypeToString(GetLastStateType());
589                         return true;
590                 } else if (macro == "LASTHOSTSTATECHANGE") {
591                         *result = Convert::ToString((long)hc->GetLastStateChange());
592                         return true;
593                 }
594
595                 hccr = hc->GetLastCheckResult();
596         }
597
598         if (hccr) {
599                 if (macro == "HOSTLATENCY") {
600                         *result = Convert::ToString(Service::CalculateLatency(hccr));
601                         return true;
602                 } else if (macro == "HOSTEXECUTIONTIME") {
603                         *result = Convert::ToString(Service::CalculateExecutionTime(hccr));
604                         return true;
605                 } else if (macro == "HOSTOUTPUT") {
606                         *result = hccr->Get("output");
607                         return true;
608                 } else if (macro == "HOSTPERFDATA") {
609                         *result = hccr->Get("performance_data_raw");
610                         return true;
611                 } else if (macro == "LASTHOSTCHECK") {
612                         *result = Convert::ToString((long)hccr->Get("schedule_start"));
613                         return true;
614                 }
615         }
616
617         Dictionary::Ptr macros = GetMacros();
618
619         if (macros && macros->Contains(macro)) {
620                 *result = macros->Get(macro);
621                 return true;
622         }
623
624         return false;
625 }