diff --git a/LibreNMS/Snmptrap/Handlers/VmwTrapUtil.php b/LibreNMS/Snmptrap/Handlers/VmwTrapUtil.php new file mode 100644 index 0000000000..eec1578a77 --- /dev/null +++ b/LibreNMS/Snmptrap/Handlers/VmwTrapUtil.php @@ -0,0 +1,69 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc. + * @author Heath Barnhart + */ + +namespace LibreNMS\Snmptrap\Handlers; + +class VmwTrapUtil +{ + /** + * Get the VMGuest hostname + * + * @param Trap $trap + * @return string + */ + public static function getGuestName($trap) + { + return $trap->getOidData($trap->findOid('VMWARE-VMINFO-MIB::vmwVmDisplayName')); + } + + /** + * Get the VMGuest ID number + * + * @param Trap $trap + * @return string + */ + public static function getGuestId($trap) + { + return $trap->getOidData($trap->findOid('VMWARE-VMINFO-MIB::vmwVmID')); + } + + /** + * Get the VMGuest configuration path + * + * @param Trap $trap + * @return string + */ + public static function getGuestConfigPath($trap) + { + return $trap->getOidData($trap->findOid('VMWARE-VMINFO-MIB::vmwVmConfigFilePath')); + } +} diff --git a/LibreNMS/Snmptrap/Handlers/VmwVmHBDetected.php b/LibreNMS/Snmptrap/Handlers/VmwVmHBDetected.php new file mode 100644 index 0000000000..04e8fdb897 --- /dev/null +++ b/LibreNMS/Snmptrap/Handlers/VmwVmHBDetected.php @@ -0,0 +1,54 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc. + * @author Heath Barnhart + */ + +namespace LibreNMS\Snmptrap\Handlers; + +use App\Models\Device; +use LibreNMS\Interfaces\SnmptrapHandler; +use LibreNMS\Snmptrap\Handlers\VmwTrapUtil; +use LibreNMS\Snmptrap\Trap; +use Log; + +class VmwVmHBDetected implements SnmptrapHandler +{ + /** + * Handle snmptrap. + * Data is pre-parsed and delivered as a Trap. + * + * @param Device $device + * @param Trap $trap + * @return void + */ + public function handle(Device $device, Trap $trap) + { + $vmGuestName = VmwTrapUtil::getGuestName($trap); + Log::event("Heartbeat from guest $vmGuestName detected", $device->device_id, 'trap', 1); + } +} diff --git a/LibreNMS/Snmptrap/Handlers/VmwVmHBLost.php b/LibreNMS/Snmptrap/Handlers/VmwVmHBLost.php new file mode 100644 index 0000000000..c6c0a408d3 --- /dev/null +++ b/LibreNMS/Snmptrap/Handlers/VmwVmHBLost.php @@ -0,0 +1,53 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc. + * @author Heath Barnhart + */ + +namespace LibreNMS\Snmptrap\Handlers; + +use App\Models\Device; +use LibreNMS\Interfaces\SnmptrapHandler; +use LibreNMS\Snmptrap\Handlers\VmwTrapUtil; +use LibreNMS\Snmptrap\Trap; +use Log; + +class VmwVmHBLost implements SnmptrapHandler +{ + /** + * Handle snmptrap. + * Data is pre-parsed and delivered as a Trap. + * + * @param Device $device + * @param Trap $trap + * @return void + */ + public function handle(Device $device, Trap $trap) + { + $vmGuestName = VmwTrapUtil::getGuestName($trap); + Log::event("Heartbeat from guest $vmGuestName lost", $device->device_id, 'trap', 4); + } +} diff --git a/LibreNMS/Snmptrap/Handlers/VmwVmPoweredOff.php b/LibreNMS/Snmptrap/Handlers/VmwVmPoweredOff.php new file mode 100644 index 0000000000..e98c7bff53 --- /dev/null +++ b/LibreNMS/Snmptrap/Handlers/VmwVmPoweredOff.php @@ -0,0 +1,57 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc. + * @author Heath Barnhart + */ + +namespace LibreNMS\Snmptrap\Handlers; + +use App\Models\Device; +use LibreNMS\Interfaces\SnmptrapHandler; +use LibreNMS\Snmptrap\Handlers\VmwTrapUtil; +use LibreNMS\Snmptrap\Trap; +use Log; + +class VmwVmPoweredOff implements SnmptrapHandler +{ + /** + * Handle snmptrap. + * Data is pre-parsed and delivered as a Trap. + * + * @param Device $device + * @param Trap $trap + * @return void + */ + public function handle(Device $device, Trap $trap) + { + $vmGuestName = VmwTrapUtil::getGuestName($trap); + + $vminfo = $device->vminfo()->where('vmwVmDisplayName', $vmGuestName)->first(); + $vminfo->vmwVmState = "powered off"; + + Log::event("Guest $vmGuestName was powered off", $device->device_id, 'trap', 2); + + $vminfo->save(); + } +} diff --git a/LibreNMS/Snmptrap/Handlers/VmwVmPoweredOn.php b/LibreNMS/Snmptrap/Handlers/VmwVmPoweredOn.php new file mode 100644 index 0000000000..77ac0a5141 --- /dev/null +++ b/LibreNMS/Snmptrap/Handlers/VmwVmPoweredOn.php @@ -0,0 +1,57 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc. + * @author Heath Barnhart + */ + +namespace LibreNMS\Snmptrap\Handlers; + +use App\Models\Device; +use LibreNMS\Interfaces\SnmptrapHandler; +use LibreNMS\Snmptrap\Handlers\VmwTrapUtil; +use LibreNMS\Snmptrap\Trap; +use Log; + +class VmwVmPoweredOn implements SnmptrapHandler +{ + /** + * Handle snmptrap. + * Data is pre-parsed and delivered as a Trap. + * + * @param Device $device + * @param Trap $trap + * @return void + */ + public function handle(Device $device, Trap $trap) + { + $vmGuestName = VmwTrapUtil::getGuestName($trap); + + $vminfo = $device->vminfo()->where('vmwVmDisplayName', $vmGuestName)->first(); + $vminfo->vmwVmState = "powered on"; + + Log::event("Guest $vmGuestName was powered on", $device->device_id, 'trap', 2); + + $vminfo->save(); + } +} diff --git a/LibreNMS/Snmptrap/Handlers/VmwVmSuspended.php b/LibreNMS/Snmptrap/Handlers/VmwVmSuspended.php new file mode 100644 index 0000000000..24000dfbe3 --- /dev/null +++ b/LibreNMS/Snmptrap/Handlers/VmwVmSuspended.php @@ -0,0 +1,57 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc. + * @author Heath Barnhart + */ + +namespace LibreNMS\Snmptrap\Handlers; + +use App\Models\Device; +use LibreNMS\Interfaces\SnmptrapHandler; +use LibreNMS\Snmptrap\Handlers\VmwTrapUtil; +use LibreNMS\Snmptrap\Trap; +use Log; + +class VmwVmSuspended implements SnmptrapHandler +{ + /** + * Handle snmptrap. + * Data is pre-parsed and delivered as a Trap. + * + * @param Device $device + * @param Trap $trap + * @return void + */ + public function handle(Device $device, Trap $trap) + { + $vmGuestName = VmwTrapUtil::getGuestName($trap); + + $vminfo = $device->vminfo()->where('vmwVmDisplayName', $vmGuestName)->first(); + $vminfo->vmwVmState = "suspended"; + + Log::event("Guest $vmGuestName has been suspended", $device->device_id, 'trap', 2); + + $vminfo->save(); + } +} diff --git a/app/Models/Device.php b/app/Models/Device.php index 7c7d771ec8..38ca180499 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -717,6 +717,11 @@ class Device extends BaseModel return $this->belongsToMany('App\Models\User', 'devices_perms', 'device_id', 'user_id'); } + public function vminfo() + { + return $this->hasMany('App\Models\Vminfo', 'device_id'); + } + public function vrfLites() { return $this->hasMany('App\Models\VrfLite', 'device_id'); diff --git a/config/snmptraps.php b/config/snmptraps.php index a3f9fc756c..7cdff7686a 100644 --- a/config/snmptraps.php +++ b/config/snmptraps.php @@ -70,5 +70,10 @@ return [ 'RUCKUS-SZ-EVENT-MIB::ruckusSZClusterBackToInServiceTrap' => \LibreNMS\Snmptrap\Handlers\RuckusSzClusterInService::class, 'SNMPv2-MIB::authenticationFailure' => \LibreNMS\Snmptrap\Handlers\AuthenticationFailure::class, 'SNMPv2-MIB::coldStart' => \LibreNMS\Snmptrap\Handlers\ColdBoot::class, + 'VMWARE-VMINFO-MIB::vmwVmHBDetected' => \LibreNMS\Snmptrap\Handlers\VmwVmHBDetected::class, + 'VMWARE-VMINFO-MIB::vmwVmHBLost' => \LibreNMS\Snmptrap\Handlers\VmwVmHBLost::class, + 'VMWARE-VMINFO-MIB::vmwVmPoweredOn' => \LibreNMS\Snmptrap\Handlers\VmwVmPoweredOn::class, + 'VMWARE-VMINFO-MIB::vmwVmPoweredOff' => \LibreNMS\Snmptrap\Handlers\VmwVmPoweredOff::class, + 'VMWARE-VMINFO-MIB::vmwVmSuspended' => \LibreNMS\Snmptrap\Handlers\VmwVmSuspended::class, ] ]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index a8024f8867..fb3a4aae64 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -9,7 +9,7 @@ | you a convenient way to create models for testing and seeding your | database. Just tell the factory how a default model should look. | -*/ + */ /** @var \Illuminate\Database\Eloquent\Factory $factory */ @@ -43,24 +43,24 @@ $factory->state(App\Models\User::class, 'read', function ($faker) { $factory->define(\App\Models\Bill::class, function (Faker\Generator $faker) { return [ - 'bill_name' => $faker->text + 'bill_name' => $faker->text, ]; }); $factory->define(\App\Models\Device::class, function (Faker\Generator $faker) { return [ - 'hostname' => $faker->domainWord.'.'.$faker->domainName, - 'ip' => $faker->randomElement([$faker->ipv4, $faker->ipv6]), - 'status' => $status = random_int(0, 1), + 'hostname' => $faker->domainWord . '.' . $faker->domainName, + 'ip' => $faker->randomElement([$faker->ipv4, $faker->ipv6]), + 'status' => $status = random_int(0, 1), 'status_reason' => $status == 0 ? $faker->randomElement(['snmp', 'icmp']) : '', // allow invalid states? ]; }); $factory->define(\App\Models\Port::class, function (Faker\Generator $faker) { return [ - 'ifIndex' => $faker->unique()->numberBetween(), - 'ifName' => $faker->text(20), - 'ifDescr' => $faker->text(255), + 'ifIndex' => $faker->unique()->numberBetween(), + 'ifName' => $faker->text(20), + 'ifDescr' => $faker->text(255), 'ifLastChange' => $faker->unixTime(), ]; }); @@ -101,7 +101,7 @@ $factory->define(\App\Models\Ipv4Address::class, function (Faker\Generator $fake $factory->define(\App\Models\Ipv4Network::class, function (Faker\Generator $faker) { return [ - 'ipv4_network' => $faker->ipv4 . '/' . $faker->numberBetween(0, 32), + 'ipv4_network' => $faker->ipv4 . '/' . $faker->numberBetween(0, 32), ]; }); @@ -119,3 +119,15 @@ $factory->define(\App\Models\Syslog::class, function (Faker\Generator $faker) { 'msg' => $faker->text(), ]; }); + +$factory->define(\App\Models\Vminfo::class, function (Faker\Generator $faker) { + return [ + 'vm_type' => $faker->text(16), + 'vmwVmVMID' => $faker->randomDigit, + 'vmwVmDisplayName' => $faker->domainWord . '.' . $faker->domainName, + 'vmwVmGuestOS' => $faker->text(128), + 'vmwVmMemSize' => $faker->randomDigit, + 'vmwVmCpus' => $faker->randomDigit, + 'vmwVmState' => $faker->randomElement(['powered on', 'powered off', 'suspended']), + ]; +}); diff --git a/includes/html/pages/vminfo.inc.php b/includes/html/pages/vminfo.inc.php index 21f1a83a0d..2bdf96b01f 100644 --- a/includes/html/pages/vminfo.inc.php +++ b/includes/html/pages/vminfo.inc.php @@ -16,7 +16,6 @@ * @author Aldemir Akpinar */ - $pagetitle[] = 'Virtual Machines'; ?>
@@ -69,6 +68,8 @@ var grid = $("#vminfo").bootgrid({ var response = 'ON'; } else if (row.powerstat == "powered off") { var response = 'OFF'; + } else if (row.powerstat == "suspended") { + var response = 'SUSPEND'; } return response; }, diff --git a/includes/html/print-vm.inc.php b/includes/html/print-vm.inc.php index bb714a4fcf..5a1c96e301 100644 --- a/includes/html/print-vm.inc.php +++ b/includes/html/print-vm.inc.php @@ -12,8 +12,10 @@ echo ''; if ($vm['vmwVmState'] == 'powered off') { echo 'OFF'; -} else { +} elseif ($vm['vmwVmState'] == 'powered on') { echo 'ON'; +} elseif ($vm['vmwVmState'] == 'suspended') { + echo 'SUSPEND'; } if ($vm['vmwVmGuestOS'] == 'E: tools not installed') { diff --git a/tests/Feature/SnmpTraps/VmwHBTest.php b/tests/Feature/SnmpTraps/VmwHBTest.php new file mode 100644 index 0000000000..febe74ac55 --- /dev/null +++ b/tests/Feature/SnmpTraps/VmwHBTest.php @@ -0,0 +1,80 @@ +. + * + * + * Tests vmwVmHBLost and vmwVmHBDetected traps from VMWare ESXi hosts. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc + * @author Heath Barnhart + */ + +namespace LibreNMS\Tests; + +use App\Models\Device; +use LibreNMS\Snmptrap\Dispatcher; +use LibreNMS\Snmptrap\Trap; +use LibreNMS\Tests\Feature\SnmpTraps\SnmpTrapTestCase; + +class VmwHBTest extends SnmpTrapTestCase +{ + public function testVmwVmHBLostTrap() + { + $device = factory(Device::class)->create(); + $guest = factory(Device::class)->create(); + + $trapText = "$device->hostname +UDP: [$device->ip]:28386->[10.10.10.100]:162 +DISMAN-EVENT-MIB::sysUpTimeInstance 5:18:30:26.00 +SNMPv2-MIB::snmpTrapOID.0 VMWARE-VMINFO-MIB::vmwVmHBLost +VMWARE-VMINFO-MIB::vmwVmID.0 28 VMWARE-VMINFO-MIB::vmwVmConfigFilePath.0 /vmfs/volumes/50101bda-eaf6ac7e-7e44-d4ae5267fb9f/$guest->hostname/$guest->hostname.vmx +VMWARE-VMINFO-MIB::vmwVmDisplayName.28 $guest->hostname +SNMP-COMMUNITY-MIB::snmpTrapAddress.0 $guest->ip +SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 \"public\" +SNMPv2-MIB::snmpTrapEnterprise.0 VMWARE-PRODUCTS-MIB::vmwESX"; + + $trap = new Trap($trapText); + $message = "Heartbeat from guest $guest->hostname lost"; + \Log::shouldReceive('event')->once()->with($message, $device->device_id, 'trap', 4); + + $this->assertTrue(Dispatcher::handle($trap), 'Could not handle VmwVmHBLostTrap'); + } + + public function testVmwVmHBDetectedTrap() + { + $device = factory(Device::class)->create(); + $guest = factory(Device::class)->create(); + + $trapText = "$device->hostname +UDP: [$device->ip]:28386->[10.10.10.100]:162 +DISMAN-EVENT-MIB::sysUpTimeInstance 5:18:30:26.00 +SNMPv2-MIB::snmpTrapOID.0 VMWARE-VMINFO-MIB::vmwVmHBDetected +VMWARE-VMINFO-MIB::vmwVmID.0 28 VMWARE-VMINFO-MIB::vmwVmConfigFilePath.0 /vmfs/volumes/50101bda-eaf6ac7e-7e44-d4ae5267fb9f/$guest->hostname/$guest->hostname.vmx +VMWARE-VMINFO-MIB::vmwVmDisplayName.28 $guest->hostname +SNMP-COMMUNITY-MIB::snmpTrapAddress.0 $guest->ip +SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 \"public\" +SNMPv2-MIB::snmpTrapEnterprise.0 VMWARE-PRODUCTS-MIB::vmwESX"; + + $trap = new Trap($trapText); + $message = "Heartbeat from guest $guest->hostname detected"; + \Log::shouldReceive('event')->once()->with($message, $device->device_id, 'trap', 1); + + $this->assertTrue(Dispatcher::handle($trap), 'Could not handle VmwVmHBDetectedTrap'); + } +} diff --git a/tests/Feature/SnmpTraps/VmwPowerStateTest.php b/tests/Feature/SnmpTraps/VmwPowerStateTest.php new file mode 100644 index 0000000000..05ae808c27 --- /dev/null +++ b/tests/Feature/SnmpTraps/VmwPowerStateTest.php @@ -0,0 +1,103 @@ +. + * + * + * Tests vmwVmPoweredOff, vmwVmPoweredOn, and vmwVmSuspended traps from VMWare ESXi hosts. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 KanREN, Inc + * @author Heath Barnhart + */ + +namespace LibreNMS\Tests; + +use App\Models\Device; +use App\Models\Vminfo; +use LibreNMS\Snmptrap\Dispatcher; +use LibreNMS\Snmptrap\Trap; +use LibreNMS\Tests\Feature\SnmpTraps\SnmpTrapTestCase; + +class VmwPowerStateTest extends SnmpTrapTestCase +{ + public function testVmwVmPoweredOffTrap() + { + $device = factory(Device::class)->create(); + $guest = factory(Vminfo::class)->create(['device_id' => $device->device_id]); + + $trapText = "$device->hostname +UDP: [$device->ip]:28386->[10.10.10.100]:162 +DISMAN-EVENT-MIB::sysUpTimeInstance 5:18:30:26.00 +SNMPv2-MIB::snmpTrapOID.0 VMWARE-VMINFO-MIB::vmwVmPoweredOff +VMWARE-VMINFO-MIB::vmwVmID.0 28 VMWARE-VMINFO-MIB::vmwVmConfigFilePath.0 /vmfs/volumes/50101bda-eaf6ac7e-7e44-d4ae5267fb9f/$guest->vmwVmDisplayName/$guest->vmwVmDisplayName.vmx +VMWARE-VMINFO-MIB::vmwVmDisplayName.28 $guest->vmwVmDisplayName +SNMP-COMMUNITY-MIB::snmpTrapAddress.0 $device->ip +SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 \"public\" +SNMPv2-MIB::snmpTrapEnterprise.0 VMWARE-PRODUCTS-MIB::vmwESX"; + + $trap = new Trap($trapText); + $message = "Guest $guest->vmwVmDisplayName was powered off"; + \Log::shouldReceive('event')->once()->with($message, $device->device_id, 'trap', 2); + + $this->assertTrue(Dispatcher::handle($trap), 'Could not handle VmwVmPoweredOffTrap'); + } + + public function testVmwVmPoweredONTrap() + { + $device = factory(Device::class)->create(); + $guest = factory(Vminfo::class)->create(['device_id' => $device->device_id]); + + $trapText = "$device->hostname +UDP: [$device->ip]:28386->[10.10.10.100]:162 +DISMAN-EVENT-MIB::sysUpTimeInstance 5:18:30:26.00 +SNMPv2-MIB::snmpTrapOID.0 VMWARE-VMINFO-MIB::vmwVmPoweredOn +VMWARE-VMINFO-MIB::vmwVmID.0 28 VMWARE-VMINFO-MIB::vmwVmConfigFilePath.0 /vmfs/volumes/50101bda-eaf6ac7e-7e44-d4ae5267fb9f/$guest->vmwVmDisplayName/$guest->vmwVmDisplayName.vmx +VMWARE-VMINFO-MIB::vmwVmDisplayName.28 $guest->vmwVmDisplayName +SNMP-COMMUNITY-MIB::snmpTrapAddress.0 $device->ip +SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 \"public\" +SNMPv2-MIB::snmpTrapEnterprise.0 VMWARE-PRODUCTS-MIB::vmwESX"; + + $trap = new Trap($trapText); + $message = "Guest $guest->vmwVmDisplayName was powered on"; + \Log::shouldReceive('event')->once()->with($message, $device->device_id, 'trap', 2); + + $this->assertTrue(Dispatcher::handle($trap), 'Could not handle VmwVmPoweredOnTrap'); + } + + public function testVmwVmSuspendedTrap() + { + $device = factory(Device::class)->create(); + $guest = factory(Vminfo::class)->create(['device_id' => $device->device_id]); + + $trapText = "$device->hostname +UDP: [$device->ip]:28386->[10.10.10.100]:162 +DISMAN-EVENT-MIB::sysUpTimeInstance 5:18:30:26.00 +SNMPv2-MIB::snmpTrapOID.0 VMWARE-VMINFO-MIB::vmwVmSuspended +VMWARE-VMINFO-MIB::vmwVmID.0 28 VMWARE-VMINFO-MIB::vmwVmConfigFilePath.0 /vmfs/volumes/50101bda-eaf6ac7e-7e44-d4ae5267fb9f/$guest->vmwVmDisplayName/$guest->vmwVmDisplayName.vmx +VMWARE-VMINFO-MIB::vmwVmDisplayName.28 $guest->vmwVmDisplayName +SNMP-COMMUNITY-MIB::snmpTrapAddress.0 $device->ip +SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 \"public\" +SNMPv2-MIB::snmpTrapEnterprise.0 VMWARE-PRODUCTS-MIB::vmwESX"; + + $trap = new Trap($trapText); + $message = "Guest $guest->vmwVmDisplayName has been suspended"; + \Log::shouldReceive('event')->once()->with($message, $device->device_id, 'trap', 2); + + $this->assertTrue(Dispatcher::handle($trap), 'Could not handle VmwVmSuspendedTrap'); + } +}