1<?php 2 3namespace Tests\Icinga\Module\Director\Objects; 4 5use Icinga\Module\Director\Data\PropertiesFilter\ArrayCustomVariablesFilter; 6use Icinga\Module\Director\Data\PropertiesFilter\CustomVariablesFilter; 7use Icinga\Module\Director\IcingaConfig\IcingaConfig; 8use Icinga\Module\Director\Objects\DirectorDatafield; 9use Icinga\Module\Director\Objects\IcingaHost; 10use Icinga\Module\Director\Objects\IcingaHostGroup; 11use Icinga\Module\Director\Objects\IcingaZone; 12use Icinga\Module\Director\Test\BaseTestCase; 13use Icinga\Exception\IcingaException; 14 15class IcingaHostTest extends BaseTestCase 16{ 17 protected $testHostName = '___TEST___host'; 18 protected $testDatafieldName = 'test5'; 19 20 public function testPropertiesCanBeSet() 21 { 22 $host = $this->host(); 23 $host->display_name = 'Something else'; 24 $this->assertEquals( 25 $host->display_name, 26 'Something else' 27 ); 28 } 29 30 public function testCanBeReplaced() 31 { 32 $host = $this->host(); 33 $newHost = IcingaHost::create( 34 array('display_name' => 'Replaced display'), 35 $this->getDb() 36 ); 37 38 $this->assertEquals( 39 count($host->vars()), 40 4 41 ); 42 $this->assertEquals( 43 $host->address, 44 '127.0.0.127' 45 ); 46 47 $host->replaceWith($newHost); 48 $this->assertEquals( 49 $host->display_name, 50 'Replaced display' 51 ); 52 $this->assertEquals( 53 $host->address, 54 null 55 ); 56 57 $this->assertEquals( 58 count($host->vars()), 59 0 60 ); 61 } 62 63 public function testCanBeMerged() 64 { 65 $host = $this->host(); 66 $newHost = IcingaHost::create( 67 array('display_name' => 'Replaced display'), 68 $this->getDb() 69 ); 70 71 $this->assertEquals( 72 count($host->vars()), 73 4 74 ); 75 $this->assertEquals( 76 $host->address, 77 '127.0.0.127' 78 ); 79 80 $host->merge($newHost); 81 $this->assertEquals( 82 $host->display_name, 83 'Replaced display' 84 ); 85 $this->assertEquals( 86 $host->address, 87 '127.0.0.127' 88 ); 89 $this->assertEquals( 90 count($host->vars()), 91 4 92 ); 93 } 94 95 public function testPropertiesCanBePreservedWhenBeingReplaced() 96 { 97 if ($this->skipForMissingDb()) { 98 return; 99 } 100 101 $db = $this->getDb(); 102 $this->host()->store($db); 103 $host = IcingaHost::load($this->testHostName, $db); 104 105 $newHost = IcingaHost::create( 106 array( 107 'display_name' => 'Replaced display', 108 'address' => '1.2.2.3', 109 'vars' => array( 110 'test1' => 'newstring', 111 'test2' => 18, 112 'initially' => 'set and then preserved', 113 ) 114 ), 115 $this->getDb() 116 ); 117 118 $preserve = array('address', 'vars.test1', 'vars.initially'); 119 $host->replaceWith($newHost, $preserve); 120 $this->assertEquals( 121 $host->address, 122 '127.0.0.127' 123 ); 124 125 $this->assertEquals( 126 $host->{'vars.test2'}, 127 18 128 ); 129 130 $this->assertEquals( 131 $host->vars()->test2->getValue(), 132 18 133 ); 134 135 $this->assertEquals( 136 $host->{'vars.initially'}, 137 'set and then preserved' 138 ); 139 140 $this->assertFalse( 141 array_key_exists('address', $host->getModifiedProperties()), 142 'Preserved property stays unmodified' 143 ); 144 145 $newHost->set('vars.initially', 'changed later on'); 146 $newHost->set('vars.test2', 19); 147 148 $host->replaceWith($newHost, $preserve); 149 $this->assertEquals( 150 $host->{'vars.initially'}, 151 'set and then preserved' 152 ); 153 154 $this->assertEquals( 155 $host->get('vars.test2'), 156 19 157 ); 158 159 160 $host->delete(); 161 } 162 163 public function testDistinctCustomVarsCanBeSetWithoutSideEffects() 164 { 165 $host = $this->host(); 166 $host->set('vars.test2', 18); 167 $this->assertEquals( 168 $host->vars()->test1->getValue(), 169 'string' 170 ); 171 $this->assertEquals( 172 $host->vars()->test2->getValue(), 173 18 174 ); 175 $this->assertEquals( 176 $host->vars()->test3->getValue(), 177 false 178 ); 179 } 180 181 public function testVarsArePersisted() 182 { 183 if ($this->skipForMissingDb()) { 184 return; 185 } 186 187 $db = $this->getDb(); 188 $this->host()->store($db); 189 $host = IcingaHost::load($this->testHostName, $db); 190 $this->assertEquals( 191 $host->vars()->test1->getValue(), 192 'string' 193 ); 194 $this->assertEquals( 195 $host->vars()->test2->getValue(), 196 17 197 ); 198 $this->assertEquals( 199 $host->vars()->test3->getValue(), 200 false 201 ); 202 $this->assertEquals( 203 $host->vars()->test4->getValue(), 204 (object) array( 205 'this' => 'is', 206 'a' => array( 207 'dict', 208 'ionary' 209 ) 210 ) 211 ); 212 } 213 214 public function testRendersCorrectly() 215 { 216 $this->assertEquals( 217 (string) $this->host(), 218 $this->loadRendered('host1') 219 ); 220 } 221 222 public function testGivesPlainObjectWithInvalidUnresolvedDependencies() 223 { 224 $props = $this->getDummyRelatedProperties(); 225 226 $host = $this->host(); 227 foreach ($props as $k => $v) { 228 $host->$k = $v; 229 } 230 231 $plain = $host->toPlainObject(); 232 foreach ($props as $k => $v) { 233 $this->assertEquals($plain->$k, $v); 234 } 235 } 236 237 public function testCorrectlyStoresLazyRelations() 238 { 239 if ($this->skipForMissingDb()) { 240 return; 241 } 242 $db = $this->getDb(); 243 $host = $this->host(); 244 $host->zone = '___TEST___zone'; 245 $this->assertEquals( 246 '___TEST___zone', 247 $host->zone 248 ); 249 250 $zone = $this->newObject('zone', '___TEST___zone'); 251 $zone->store($db); 252 253 $host->store($db); 254 $host->delete(); 255 $zone->delete(); 256 } 257 258 /** 259 * @expectedException \RuntimeException 260 */ 261 public function testFailsToStoreWithMissingLazyRelations() 262 { 263 if ($this->skipForMissingDb()) { 264 return; 265 } 266 $db = $this->getDb(); 267 $host = $this->host(); 268 $host->zone = '___TEST___zone'; 269 $host->store($db); 270 } 271 272 public function testHandlesUnmodifiedProperties() 273 { 274 $this->markTestSkipped('Currently broken, needs to be fixed'); 275 276 if ($this->skipForMissingDb()) { 277 return; 278 } 279 280 $db = $this->getDb(); 281 $host = $this->host(); 282 $host->store($db); 283 284 $parent = $this->newObject('host', '___TEST___parent'); 285 $parent->store($db); 286 $host->imports = '___TEST___parent'; 287 288 $host->store($db); 289 290 $plain = $host->getPlainUnmodifiedObject(); 291 $this->assertEquals( 292 'string', 293 $plain->vars->test1 294 ); 295 $host->vars()->set('test1', 'nada'); 296 297 $host->store(); 298 299 $plain = $host->getPlainUnmodifiedObject(); 300 $this->assertEquals( 301 'nada', 302 $plain->vars->test1 303 ); 304 305 $host->vars()->set('test1', 'string'); 306 $plain = $host->getPlainUnmodifiedObject(); 307 $this->assertEquals( 308 'nada', 309 $plain->vars->test1 310 ); 311 312 $plain = $host->getPlainUnmodifiedObject(); 313 $test = IcingaHost::create((array) $plain); 314 315 $this->assertEquals( 316 $this->loadRendered('host3'), 317 (string) $test 318 ); 319 320 $host->delete(); 321 $parent->delete(); 322 } 323 324 public function testRendersWithInvalidUnresolvedDependencies() 325 { 326 $newHost = $this->host(); 327 $newHost->zone = 'invalid'; 328 $newHost->check_command = 'unknown'; 329 $newHost->event_command = 'What event?'; 330 $newHost->check_period = 'Not time is a good time @ nite'; 331 $newHost->command_endpoint = 'nirvana'; 332 333 $this->assertEquals( 334 (string) $newHost, 335 $this->loadRendered('host2') 336 ); 337 } 338 339 /** 340 * @expectedException \RuntimeException 341 */ 342 public function testFailsToStoreWithInvalidUnresolvedDependencies() 343 { 344 if ($this->skipForMissingDb()) { 345 return; 346 } 347 348 $host = $this->host(); 349 $host->zone = 'invalid'; 350 $host->store($this->getDb()); 351 } 352 353 public function testRendersToTheCorrectZone() 354 { 355 if ($this->skipForMissingDb()) { 356 return; 357 } 358 359 $db = $this->getDb(); 360 $host = $this->host()->setConnection($db); 361 $masterzone = $db->getMasterZoneName(); 362 363 $config = new IcingaConfig($db); 364 $host->renderToConfig($config); 365 $this->assertEquals( 366 array('zones.d/' . $masterzone . '/hosts.conf'), 367 $config->getFileNames() 368 ); 369 370 $zone = $this->newObject('zone', '___TEST___zone'); 371 $zone->store($db); 372 373 $config = new IcingaConfig($db); 374 $host->zone = '___TEST___zone'; 375 $host->renderToConfig($config); 376 $this->assertEquals( 377 array('zones.d/___TEST___zone/hosts.conf'), 378 $config->getFileNames() 379 ); 380 381 $host->has_agent = true; 382 $host->master_should_connect = true; 383 $host->accept_config = true; 384 385 $config = new IcingaConfig($db); 386 $host->renderToConfig($config); 387 $this->assertEquals( 388 array( 389 'zones.d/___TEST___zone/hosts.conf', 390 'zones.d/___TEST___zone/agent_endpoints.conf', 391 'zones.d/___TEST___zone/agent_zones.conf' 392 ), 393 $config->getFileNames() 394 ); 395 396 $host->object_type = 'template'; 397 $host->zone_id = null; 398 399 $config = new IcingaConfig($db); 400 $host->renderToConfig($config); 401 $this->assertEquals( 402 array('zones.d/director-global/host_templates.conf'), 403 $config->getFileNames() 404 ); 405 } 406 407 public function testWhetherTwoHostsCannotBeStoredWithTheSameApiKey() 408 { 409 if ($this->skipForMissingDb()) { 410 return; 411 } 412 413 $db = $this->getDb(); 414 $a = IcingaHost::create(array( 415 'object_name' => '___TEST___a', 416 'object_type' => 'object', 417 'api_key' => 'a' 418 ), $db); 419 $b = IcingaHost::create(array( 420 'object_name' => '___TEST___b', 421 'object_type' => 'object', 422 'api_key' => 'a' 423 ), $db); 424 425 $a->store(); 426 try { 427 $b->store(); 428 } catch (\RuntimeException $e) { 429 $msg = $e->getMessage(); 430 $matchMysql = strpos( 431 $msg, 432 "Duplicate entry 'a' for key 'api_key'" 433 ) !== false; 434 435 $matchPostgres = strpos( 436 $msg, 437 'Unique violation' 438 ) !== false; 439 440 $this->assertTrue( 441 $matchMysql || $matchPostgres, 442 'Exception message does not tell about unique constraint violation' 443 ); 444 $a->delete(); 445 } 446 } 447 448 public function testWhetherHostCanBeLoadedWithValidApiKey() 449 { 450 if ($this->skipForMissingDb()) { 451 return; 452 } 453 454 $db = $this->getDb(); 455 $a = IcingaHost::create(array( 456 'object_name' => '___TEST___a', 457 'object_type' => 'object', 458 'api_key' => 'a1a1a1' 459 ), $db); 460 $b = IcingaHost::create(array( 461 'object_name' => '___TEST___b', 462 'object_type' => 'object', 463 'api_key' => 'b1b1b1' 464 ), $db); 465 $a->store(); 466 $b->store(); 467 468 $this->assertEquals( 469 IcingaHost::loadWithApiKey('b1b1b1', $db)->object_name, 470 '___TEST___b' 471 ); 472 473 $a->delete(); 474 $b->delete(); 475 } 476 477 /** 478 * @expectedException \Icinga\Exception\NotFoundError 479 */ 480 public function testWhetherInvalidApiKeyThrows404() 481 { 482 if ($this->skipForMissingDb()) { 483 return; 484 } 485 486 $db = $this->getDb(); 487 IcingaHost::loadWithApiKey('No___such___key', $db); 488 } 489 490 public function testEnumProperties() 491 { 492 if ($this->skipForMissingDb()) { 493 return; 494 } 495 496 $db = $this->getDb(); 497 $properties = IcingaHost::enumProperties($db); 498 499 $this->assertEquals( 500 array( 501 'Host properties' => $this->getDefaultHostProperties() 502 ), 503 $properties 504 ); 505 } 506 507 public function testEnumPropertiesWithCustomVars() 508 { 509 if ($this->skipForMissingDb()) { 510 return; 511 } 512 513 $db = $this->getDb(); 514 515 $host = $this->host(); 516 $host->store($db); 517 518 $properties = IcingaHost::enumProperties($db); 519 $this->assertEquals( 520 array( 521 'Host properties' => $this->getDefaultHostProperties(), 522 'Custom variables' => array( 523 'vars.test1' => 'test1', 524 'vars.test2' => 'test2', 525 'vars.test3' => 'test3', 526 'vars.test4' => 'test4' 527 ) 528 ), 529 $properties 530 ); 531 } 532 533 public function testEnumPropertiesWithPrefix() 534 { 535 if ($this->skipForMissingDb()) { 536 return; 537 } 538 539 $db = $this->getDb(); 540 541 $host = $this->host(); 542 $host->store($db); 543 544 $properties = IcingaHost::enumProperties($db, 'host.'); 545 $this->assertEquals( 546 array( 547 'Host properties' => $this->getDefaultHostProperties('host.'), 548 'Custom variables' => array( 549 'host.vars.test1' => 'test1', 550 'host.vars.test2' => 'test2', 551 'host.vars.test3' => 'test3', 552 'host.vars.test4' => 'test4' 553 ) 554 ), 555 $properties 556 ); 557 } 558 559 public function testEnumPropertiesWithFilter() 560 { 561 if ($this->skipForMissingDb()) { 562 return; 563 } 564 565 $db = $this->getDb(); 566 567 DirectorDatafield::create(array( 568 'varname' => $this->testDatafieldName, 569 'caption' => 'Blah', 570 'description' => '', 571 'datatype' => 'Icinga\Module\Director\DataType\DataTypeArray', 572 'format' => 'json' 573 ))->store($db); 574 575 $host = $this->host(); 576 $host->{'vars.test5'} = array('a', '1'); 577 $host->store($db); 578 579 $properties = IcingaHost::enumProperties($db, '', new CustomVariablesFilter()); 580 $this->assertEquals( 581 array( 582 'Custom variables' => array( 583 'vars.test1' => 'test1', 584 'vars.test2' => 'test2', 585 'vars.test3' => 'test3', 586 'vars.test4' => 'test4', 587 'vars.test5' => 'test5 (Blah)' 588 ) 589 ), 590 $properties 591 ); 592 } 593 594 public function testEnumPropertiesWithArrayFilter() 595 { 596 if ($this->skipForMissingDb()) { 597 return; 598 } 599 600 $db = $this->getDb(); 601 602 DirectorDatafield::create(array( 603 'varname' => $this->testDatafieldName, 604 'caption' => 'Blah', 605 'description' => '', 606 'datatype' => 'Icinga\Module\Director\DataType\DataTypeArray', 607 'format' => 'json' 608 ))->store($db); 609 610 $host = $this->host(); 611 $host->{'vars.test5'} = array('a', '1'); 612 $host->store($db); 613 614 $properties = IcingaHost::enumProperties($db, '', new ArrayCustomVariablesFilter()); 615 $this->assertEquals( 616 array( 617 'Custom variables' => array( 618 'vars.test5' => 'test5 (Blah)' 619 ) 620 ), 621 $properties 622 ); 623 } 624 625 public function testMergingObjectKeepsGroupsIfNotGiven() 626 { 627 $one = IcingaHostGroup::create([ 628 'object_name' => 'one', 629 'object_type' => 'object', 630 ]); 631 $two = IcingaHostGroup::create([ 632 'object_name' => 'two', 633 'object_type' => 'object', 634 ]); 635 $a = IcingaHost::create([ 636 'object_name' => 'one', 637 'object_type' => 'object', 638 'imports' => [], 639 'address' => '127.0.0.2', 640 'groups' => [$one, $two] 641 ]); 642 643 $b = IcingaHost::create([ 644 'object_name' => 'one', 645 'object_type' => 'object', 646 'imports' => [], 647 'address' => '127.0.0.42', 648 ]); 649 650 $a->merge($b); 651 $this->assertEquals( 652 '127.0.0.42', 653 $a->get('address') 654 ); 655 $this->assertEquals( 656 ['one', 'two'], 657 $a->getGroups() 658 ); 659 } 660 661 protected function getDummyRelatedProperties() 662 { 663 return array( 664 'zone' => 'invalid', 665 'check_command' => 'unknown', 666 'event_command' => 'What event?', 667 'check_period' => 'Not time is a good time @ nite', 668 'command_endpoint' => 'nirvana', 669 ); 670 } 671 672 protected function host() 673 { 674 return IcingaHost::create(array( 675 'object_name' => $this->testHostName, 676 'object_type' => 'object', 677 'address' => '127.0.0.127', 678 'display_name' => 'Whatever', 679 'vars' => array( 680 'test1' => 'string', 681 'test2' => 17, 682 'test3' => false, 683 'test4' => (object) array( 684 'this' => 'is', 685 'a' => array( 686 'dict', 687 'ionary' 688 ) 689 ) 690 ) 691 ), $this->getDb()); 692 } 693 694 protected function getDefaultHostProperties($prefix = '') 695 { 696 return array( 697 "${prefix}name" => "name", 698 "${prefix}action_url" => "action_url", 699 "${prefix}address" => "address", 700 "${prefix}address6" => "address6", 701 "${prefix}api_key" => "api_key", 702 "${prefix}check_command" => "check_command", 703 "${prefix}check_interval" => "check_interval", 704 "${prefix}check_period" => "check_period", 705 "${prefix}check_timeout" => "check_timeout", 706 "${prefix}command_endpoint" => "command_endpoint", 707 "${prefix}display_name" => "display_name", 708 "${prefix}enable_active_checks" => "enable_active_checks", 709 "${prefix}enable_event_handler" => "enable_event_handler", 710 "${prefix}enable_flapping" => "enable_flapping", 711 "${prefix}enable_notifications" => "enable_notifications", 712 "${prefix}enable_passive_checks" => "enable_passive_checks", 713 "${prefix}enable_perfdata" => "enable_perfdata", 714 "${prefix}event_command" => "event_command", 715 "${prefix}flapping_threshold_high" => "flapping_threshold_high", 716 "${prefix}flapping_threshold_low" => "flapping_threshold_low", 717 "${prefix}icon_image" => "icon_image", 718 "${prefix}icon_image_alt" => "icon_image_alt", 719 "${prefix}max_check_attempts" => "max_check_attempts", 720 "${prefix}notes" => "notes", 721 "${prefix}notes_url" => "notes_url", 722 "${prefix}retry_interval" => "retry_interval", 723 "${prefix}volatile" => "volatile", 724 "${prefix}zone" => "zone", 725 "${prefix}groups" => "Groups", 726 "${prefix}templates" => "templates" 727 ); 728 } 729 protected function loadRendered($name) 730 { 731 return file_get_contents(__DIR__ . '/rendered/' . $name . '.out'); 732 } 733 734 public function tearDown() 735 { 736 if ($this->hasDb()) { 737 $db = $this->getDb(); 738 $kill = array($this->testHostName, '___TEST___parent', '___TEST___a', '___TEST___b'); 739 foreach ($kill as $name) { 740 if (IcingaHost::exists($name, $db)) { 741 IcingaHost::load($name, $db)->delete(); 742 } 743 } 744 745 $kill = array('___TEST___zone'); 746 foreach ($kill as $name) { 747 if (IcingaZone::exists($name, $db)) { 748 IcingaZone::load($name, $db)->delete(); 749 } 750 } 751 752 $this->deleteDatafields(); 753 } 754 } 755 756 protected function deleteDatafields() 757 { 758 $db = $this->getDb(); 759 $dbAdapter = $db->getDbAdapter(); 760 $kill = array($this->testDatafieldName); 761 762 foreach ($kill as $name) { 763 $query = $dbAdapter->select() 764 ->from('director_datafield') 765 ->where('varname = ?', $name); 766 foreach (DirectorDatafield::loadAll($db, $query, 'id') as $datafield) { 767 $datafield->delete(); 768 } 769 } 770 } 771} 772