From 6baf19c933604491e107d48e66bbfb61a9887b4c Mon Sep 17 00:00:00 2001 From: qgarnier Date: Thu, 7 Oct 2021 10:06:38 +0200 Subject: [PATCH] enh(hyperv): add scvmm discovery mode (#3178) --- .../hyperv/2012/local/mode/scvmmdiscovery.pm | 264 ++++++++++++++++++ .../local/mode/scvmmintegrationservice.pm | 4 +- .../hyperv/2012/local/mode/scvmmsnapshot.pm | 4 +- .../hyperv/2012/local/mode/scvmmvmstatus.pm | 4 +- apps/microsoft/hyperv/2012/local/plugin.pm | 1 + .../powershell/hyperv/2012/scvmmdiscovery.pm | 121 ++++++++ .../hyperv/2012/scvmmintegrationservice.pm | 6 +- .../powershell/hyperv/2012/scvmmsnapshot.pm | 6 +- .../powershell/hyperv/2012/scvmmvmstatus.pm | 6 +- 9 files changed, 407 insertions(+), 9 deletions(-) create mode 100644 apps/microsoft/hyperv/2012/local/mode/scvmmdiscovery.pm create mode 100644 centreon/common/powershell/hyperv/2012/scvmmdiscovery.pm diff --git a/apps/microsoft/hyperv/2012/local/mode/scvmmdiscovery.pm b/apps/microsoft/hyperv/2012/local/mode/scvmmdiscovery.pm new file mode 100644 index 000000000..2cb55cf3e --- /dev/null +++ b/apps/microsoft/hyperv/2012/local/mode/scvmmdiscovery.pm @@ -0,0 +1,264 @@ +# +# Copyright 2021 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::microsoft::hyperv::2012::local::mode::scvmmdiscovery; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; +use centreon::plugins::misc; +use centreon::common::powershell::hyperv::2012::scvmmdiscovery; +use apps::microsoft::hyperv::2012::local::mode::resources::types qw($scvmm_vm_status); +use JSON::XS; + +sub prefix_vm_output { + my ($self, %options) = @_; + + return "VM '" . $options{instance_value}->{vm} . "' "; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'prettify' => { name => 'prettify' }, + 'resource-type:s' => { name => 'resource_type', type => 'vm' }, + 'scvmm-hostname:s' => { name => 'scvmm_hostname' }, + 'scvmm-username:s' => { name => 'scvmm_username' }, + 'scvmm-password:s' => { name => 'scvmm_password' }, + 'scvmm-port:s' => { name => 'scvmm_port', default => 8100 }, + 'timeout:s' => { name => 'timeout', default => 90 }, + 'command:s' => { name => 'command', default => 'powershell.exe' }, + 'command-path:s' => { name => 'command_path' }, + 'command-options:s' => { name => 'command_options', default => '-InputFormat none -NoLogo -EncodedCommand' }, + 'no-ps' => { name => 'no_ps' }, + 'ps-exec-only' => { name => 'ps_exec_only' }, + 'ps-display' => { name => 'ps_display' } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); + + if (!defined($self->{option_results}->{resource_type}) || $self->{option_results}->{resource_type} eq '') { + $self->{option_results}->{resource_type} = 'vm'; + } + if ($self->{option_results}->{resource_type} !~ /^vm|host$/) { + $self->{output}->add_option_msg(short_msg => 'unknown resource type'); + $self->{output}->option_exit(); + } + + foreach my $label (('scvmm_username', 'scvmm_password', 'scvmm_port')) { + if (!defined($self->{option_results}->{$label}) || $self->{option_results}->{$label} eq '') { + my ($label_opt) = $label; + $label_opt =~ tr/_/-/; + $self->{output}->add_option_msg(short_msg => "Need to specify --" . $label_opt . " option."); + $self->{output}->option_exit(); + } + } +} + +sub powershell_exec { + my ($self, %options) = @_; + + if (!defined($self->{option_results}->{no_ps})) { + my $ps = centreon::common::powershell::hyperv::2012::scvmmdiscovery::get_powershell( + scvmm_hostname => $self->{option_results}->{scvmm_hostname}, + scvmm_username => $self->{option_results}->{scvmm_username}, + scvmm_password => $self->{option_results}->{scvmm_password}, + scvmm_port => $self->{option_results}->{scvmm_port} + ); + if (defined($self->{option_results}->{ps_display})) { + $self->{output}->output_add( + severity => 'OK', + short_msg => $ps + ); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); + } + + $self->{option_results}->{command_options} .= " " . centreon::plugins::misc::powershell_encoded($ps); + } + + my ($stdout) = centreon::plugins::misc::execute( + output => $self->{output}, + options => $self->{option_results}, + command => $self->{option_results}->{command}, + command_path => $self->{option_results}->{command_path}, + command_options => $self->{option_results}->{command_options} + ); + if (defined($self->{option_results}->{ps_exec_only})) { + $self->{output}->output_add( + severity => 'OK', + short_msg => $stdout + ); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); + } + + my $decoded; + eval { + $decoded = JSON::XS->new->decode($self->{output}->decode($stdout)); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode json response: $@"); + $self->{output}->option_exit(); + } + + return $decoded; +} + +sub run { + my ($self, %options) = @_; + + my $decoded = $self->powershell_exec(); + + my $hosts = {}; + foreach my $entry (@$decoded) { + next if ($entry->{type} ne 'host'); + $hosts->{ $entry->{id} } = { cluster_name => $entry->{clusterName}, name => $entry->{name} }; + } + + my $disco_data; + my $disco_stats; + foreach my $entry (@$decoded) { + my $item = {}; + $item->{type} = $entry->{type}; + + if ($self->{option_results}->{resource_type} eq 'vm' && $entry->{type} eq 'vm') { + $item->{id} = $entry->{vmId}; + $item->{name} = $entry->{name}; + $item->{description} = $entry->{description}; + $item->{operating_system} = $entry->{operatingSystem}; + $item->{status} = $scvmm_vm_status->{ $entry->{status} }; + $item->{hostgroup_path} = $entry->{hostGroupPath}; + $item->{enabled} = ($entry->{enabled} =~ /True|1/i) ? 'yes' : 'no'; + $item->{computer_name} = $entry->{computerName}; + $item->{tag} = $entry->{tag}; + $item->{ipv4_addresses} = $entry->{ipv4Addresses}; + $item->{vmhost_name} = $hosts->{ $entry->{vmHostId} }->{name}; + $item->{cluster_name} = $hosts->{ $entry->{vmHostId} }->{cluster_name}; + } elsif ($self->{option_results}->{resource_type} eq 'host' && $entry->{type} eq 'host') { + $item->{id} = $entry->{id}; + $item->{name} = $entry->{name}; + $item->{description} = $entry->{description}; + $item->{fqdn} = $entry->{FQDN}; + $item->{cluster_name} = $entry->{clusterName}; + $item->{operating_system} = $entry->{operatingSystem}; + } + + push @$disco_data, $item; + } + + $disco_stats->{end_time} = time(); + $disco_stats->{duration} = $disco_stats->{end_time} - $disco_stats->{start_time}; + $disco_stats->{discovered_items} = scalar(@$disco_data); + $disco_stats->{results} = $disco_data; + + my $encoded_data; + eval { + if (defined($self->{option_results}->{prettify})) { + $encoded_data = JSON::XS->new->utf8->pretty->encode($disco_stats); + } else { + $encoded_data = JSON::XS->new->utf8->encode($disco_stats); + } + }; + if ($@) { + $encoded_data = '{"code":"encode_error","message":"Cannot encode discovered data into JSON format"}'; + } + + $self->{output}->output_add(short_msg => $encoded_data); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1); + $self->{output}->exit(); +} + +1; + +__END__ + +=head1 MODE + +Resources discovery. + +=over 8 + +=item B<--prettify> + +Prettify JSON output. + +=item B<--resource-type> + +Choose the type of resources +to discover (Can be: 'host', 'vm') (Mandatory). + +=item B<--scvmm-hostname> + +SCVMM hostname. + +=item B<--scvmm-username> + +SCVMM username (Required). + +=item B<--scvmm-password> + +SCVMM password (Required). + +=item B<--scvmm-port> + +SCVMM port (Default: 8100). + +=item B<--timeout> + +Set timeout time for command execution (Default: 90 sec) + +=item B<--no-ps> + +Don't encode powershell. To be used with --command and 'type' command. + +=item B<--command> + +Command to get information (Default: 'powershell.exe'). +Can be changed if you have output in a file. To be used with --no-ps option!!! + +=item B<--command-path> + +Command path (Default: none). + +=item B<--command-options> + +Command options (Default: '-InputFormat none -NoLogo -EncodedCommand'). + +=item B<--ps-display> + +Display powershell script. + +=item B<--ps-exec-only> + +Print powershell output. + +=back + +=cut diff --git a/apps/microsoft/hyperv/2012/local/mode/scvmmintegrationservice.pm b/apps/microsoft/hyperv/2012/local/mode/scvmmintegrationservice.pm index a817945a2..9a333ca29 100644 --- a/apps/microsoft/hyperv/2012/local/mode/scvmmintegrationservice.pm +++ b/apps/microsoft/hyperv/2012/local/mode/scvmmintegrationservice.pm @@ -180,7 +180,7 @@ sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); - foreach my $label (('scvmm_hostname', 'scvmm_username', 'scvmm_password', 'scvmm_port')) { + foreach my $label (('scvmm_username', 'scvmm_password', 'scvmm_port')) { if (!defined($self->{option_results}->{$label}) || $self->{option_results}->{$label} eq '') { my ($label_opt) = $label; $label_opt =~ tr/_/-/; @@ -297,7 +297,7 @@ Check virtual machine integration services on SCVMM. =item B<--scvmm-hostname> -SCVMM hostname (Required). +SCVMM hostname. =item B<--scvmm-username> diff --git a/apps/microsoft/hyperv/2012/local/mode/scvmmsnapshot.pm b/apps/microsoft/hyperv/2012/local/mode/scvmmsnapshot.pm index ded8443ae..51fa2b3e7 100644 --- a/apps/microsoft/hyperv/2012/local/mode/scvmmsnapshot.pm +++ b/apps/microsoft/hyperv/2012/local/mode/scvmmsnapshot.pm @@ -87,7 +87,7 @@ sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); - foreach my $label (('scvmm_hostname', 'scvmm_username', 'scvmm_password', 'scvmm_port')) { + foreach my $label (('scvmm_username', 'scvmm_password', 'scvmm_port')) { if (!defined($self->{option_results}->{$label}) || $self->{option_results}->{$label} eq '') { my ($label_opt) = $label; $label_opt =~ tr/_/-/; @@ -200,7 +200,7 @@ Check virtual machine snapshots on SCVMM. =item B<--scvmm-hostname> -SCVMM hostname (Required). +SCVMM hostname. =item B<--scvmm-username> diff --git a/apps/microsoft/hyperv/2012/local/mode/scvmmvmstatus.pm b/apps/microsoft/hyperv/2012/local/mode/scvmmvmstatus.pm index 509955338..f3800ef3c 100644 --- a/apps/microsoft/hyperv/2012/local/mode/scvmmvmstatus.pm +++ b/apps/microsoft/hyperv/2012/local/mode/scvmmvmstatus.pm @@ -91,7 +91,7 @@ sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); - foreach my $label (('scvmm_hostname', 'scvmm_username', 'scvmm_password', 'scvmm_port')) { + foreach my $label (('scvmm_username', 'scvmm_password', 'scvmm_port')) { if (!defined($self->{option_results}->{$label}) || $self->{option_results}->{$label} eq '') { my ($label_opt) = $label; $label_opt =~ tr/_/-/; @@ -199,7 +199,7 @@ Check virtual machine status on SCVMM. =item B<--scvmm-hostname> -SCVMM hostname (Required). +SCVMM hostname. =item B<--scvmm-username> diff --git a/apps/microsoft/hyperv/2012/local/plugin.pm b/apps/microsoft/hyperv/2012/local/plugin.pm index 58a9bd611..5fe5b3d19 100644 --- a/apps/microsoft/hyperv/2012/local/plugin.pm +++ b/apps/microsoft/hyperv/2012/local/plugin.pm @@ -36,6 +36,7 @@ sub new { 'node-replication' => 'apps::microsoft::hyperv::2012::local::mode::nodereplication', 'node-snapshot' => 'apps::microsoft::hyperv::2012::local::mode::nodesnapshot', 'node-vm-status' => 'apps::microsoft::hyperv::2012::local::mode::nodevmstatus', + 'scvmm-discovery' => 'apps::microsoft::hyperv::2012::local::mode::scvmmdiscovery', 'scvmm-integration-service' => 'apps::microsoft::hyperv::2012::local::mode::scvmmintegrationservice', 'scvmm-snapshot' => 'apps::microsoft::hyperv::2012::local::mode::scvmmsnapshot', 'scvmm-vm-status' => 'apps::microsoft::hyperv::2012::local::mode::scvmmvmstatus' diff --git a/centreon/common/powershell/hyperv/2012/scvmmdiscovery.pm b/centreon/common/powershell/hyperv/2012/scvmmdiscovery.pm new file mode 100644 index 000000000..79040171d --- /dev/null +++ b/centreon/common/powershell/hyperv/2012/scvmmdiscovery.pm @@ -0,0 +1,121 @@ +# +# Copyright 2021 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::common::powershell::hyperv::2012::scvmmdiscovery; + +use strict; +use warnings; +use centreon::common::powershell::functions; + +sub get_powershell { + my (%options) = @_; + + my $hostname = '$env:computername'; + if (defined($options{scvmm_hostname}) && $options{scvmm_hostname} ne '') { + $hostname = '"' . $options{scvmm_hostname} . '"'; + } + my $ps = ' +$culture = new-object "System.Globalization.CultureInfo" "en-us" +[System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture +'; + + $ps .= centreon::common::powershell::functions::escape_jsonstring(%options); + $ps .= centreon::common::powershell::functions::convert_to_json(%options); + + $ps .= ' +$ProgressPreference = "SilentlyContinue" + +Try { + $ErrorActionPreference = "Stop" + Import-Module -Name "virtualmachinemanager" + + $username = "' . $options{scvmm_username} . '" + $password = ConvertTo-SecureString "' . $options{scvmm_password} . '" -AsPlainText -Force + $UserCredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $username,$password + + $connection = Get-VMMServer -ComputerName ' . $hostname . ' -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential + $vms = Get-SCVirtualMachine -VMMServer $connection + + $items = New-Object System.Collections.Generic.List[Hashtable]; + Foreach ($vm in $vms) { + $item = @{} + + $item.type = "vm" + $item.vmId = $vm.VMId + $item.name = $vm.Name + $desc = $vm.Description -replace "\r","" + $item.description = $desc + $item.operatingSystem = $vm.OperatingSystem.ToString() + $item.status = $vm.Status.value__ + $item.hostGroupPath = $vm.HostGroupPath + $item.enabled = $vm.enabled + $item.computerName = $vm.ComputerName + $item.tag = $vm.Tag + $item.vmHostId = $vm.VMHost.ID + + $ipv4Addresses = @() + if ($vm.Status -eq "Running") { + Foreach ($adapter in $vm.VirtualNetworkAdapters) { + $ipv4Addresses += $adapter.IPv4Addresses + } + } + $item.ipv4Addresses = $ipv4Addresses + + $items.Add($item) + } + + $hosts = Get-SCVmHost -VMMServer $connection + Foreach ($host in $hosts) { + $item = @{} + + $item.type = "host" + $item.id = $host.ID + $item.name = $host.Name + $desc = $host.Description -replace "\r","" + $item.description = $desc + $item.FQDN = $host.FQDN + $item.clusterName = $host.HostCluster.Name + $item.operatingSystem = $host.OperatingSystem.Name + + $items.Add($item) + } + + $jsonString = $items | ConvertTo-JSON-20 -forceArray $true + Write-Host $jsonString +} Catch { + Write-Host $Error[0].Exception + exit 1 +} + +exit 0 +'; + + return $ps; +} + +1; + +__END__ + +=head1 DESCRIPTION + +Method to get hyper-v informations. + +=cut diff --git a/centreon/common/powershell/hyperv/2012/scvmmintegrationservice.pm b/centreon/common/powershell/hyperv/2012/scvmmintegrationservice.pm index 09fd2242c..ef0db6b27 100644 --- a/centreon/common/powershell/hyperv/2012/scvmmintegrationservice.pm +++ b/centreon/common/powershell/hyperv/2012/scvmmintegrationservice.pm @@ -27,6 +27,10 @@ use centreon::common::powershell::functions; sub get_powershell { my (%options) = @_; + my $hostname = '$env:computername'; + if (defined($options{scvmm_hostname}) && $options{scvmm_hostname} ne '') { + $hostname = '"' . $options{scvmm_hostname} . '"'; + } my $ps = ' $culture = new-object "System.Globalization.CultureInfo" "en-us" [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture @@ -46,7 +50,7 @@ Try { $password = ConvertTo-SecureString "' . $options{scvmm_password} . '" -AsPlainText -Force $UserCredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $username,$password - $connection = Get-VMMServer -ComputerName "' . $options{scvmm_hostname} . '" -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential + $connection = Get-VMMServer -ComputerName ' . $hostname . ' -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential $vms = Get-SCVirtualMachine -VMMServer $connection $items = New-Object System.Collections.Generic.List[Hashtable]; diff --git a/centreon/common/powershell/hyperv/2012/scvmmsnapshot.pm b/centreon/common/powershell/hyperv/2012/scvmmsnapshot.pm index 438c8646c..e6957e2cb 100644 --- a/centreon/common/powershell/hyperv/2012/scvmmsnapshot.pm +++ b/centreon/common/powershell/hyperv/2012/scvmmsnapshot.pm @@ -27,6 +27,10 @@ use centreon::common::powershell::functions; sub get_powershell { my (%options) = @_; + my $hostname = '$env:computername'; + if (defined($options{scvmm_hostname}) && $options{scvmm_hostname} ne '') { + $hostname = '"' . $options{scvmm_hostname} . '"'; + } my $ps = ' $culture = new-object "System.Globalization.CultureInfo" "en-us" [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture @@ -46,7 +50,7 @@ Try { $password = ConvertTo-SecureString "' . $options{scvmm_password} . '" -AsPlainText -Force $UserCredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $username,$password - $connection = Get-VMMServer -ComputerName "' . $options{scvmm_hostname} . '" -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential + $connection = Get-VMMServer -ComputerName ' . $hostname . ' -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential $vms = Get-SCVirtualMachine -VMMServer $connection $items = New-Object System.Collections.Generic.List[Hashtable]; diff --git a/centreon/common/powershell/hyperv/2012/scvmmvmstatus.pm b/centreon/common/powershell/hyperv/2012/scvmmvmstatus.pm index 221d17217..f13d31a91 100644 --- a/centreon/common/powershell/hyperv/2012/scvmmvmstatus.pm +++ b/centreon/common/powershell/hyperv/2012/scvmmvmstatus.pm @@ -27,6 +27,10 @@ use centreon::common::powershell::functions; sub get_powershell { my (%options) = @_; + my $hostname = '$env:computername'; + if (defined($options{scvmm_hostname}) && $options{scvmm_hostname} ne '') { + $hostname = '"' . $options{scvmm_hostname} . '"'; + } my $ps = ' $culture = new-object "System.Globalization.CultureInfo" "en-us" [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture @@ -46,7 +50,7 @@ Try { $password = ConvertTo-SecureString "' . $options{scvmm_password} . '" -AsPlainText -Force $UserCredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $username,$password - $connection = Get-VMMServer -ComputerName "' . $options{scvmm_hostname} . '" -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential + $connection = Get-VMMServer -ComputerName ' . $hostname . ' -TCPPort ' . $options{scvmm_port} . ' -Credential $UserCredential $vms = Get-SCVirtualMachine -VMMServer $connection $items = New-Object System.Collections.Generic.List[Hashtable];