Skip to content

Commit a3dbbd1

Browse files
authored
feat(cloud::linux::libvirt::local::plugin) new plugin (#6049)
Refs: CTOR-668
1 parent 473170c commit a3dbbd1

File tree

21 files changed

+2659
-3
lines changed

21 files changed

+2659
-3
lines changed

src/centreon/plugins/misc.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use feature 'state';
3333

3434
our @EXPORT_OK = qw/change_seconds
3535
check_security_whitelist
36+
convert_bytes
3637
flatten_arrays
3738
flatten_to_hash
3839
graphql_escape

src/centreon/plugins/script_custom.pm

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,26 @@ sub init {
155155
$self->{pass_mgr}->manage_options(option_results => $self->{option_results}) if (defined($self->{pass_mgr}));
156156

157157
push @{$self->{custommode_stored}}, $self->{custommode_current};
158-
$self->{custommode_current}->set_options(option_results => $self->{option_results});
159-
$self->{custommode_current}->set_defaults(default => $self->{customdefault});
158+
159+
# Call custom mode set_options if it's defined, otherwise set option_results directly
160+
if ($self->{custommode_current}->can('set_options')) {
161+
$self->{custommode_current}->set_options(option_results => $self->{option_results});
162+
} else {
163+
$self->{custommode_current}->{option_results} = $self->{option_results};
164+
}
165+
166+
# Call set_defaults only if it's defined in the custom mode
167+
$self->{custommode_current}->set_defaults(default => $self->{customdefault})
168+
if $self->{custommode_current}->can('set_defaults');
160169

161170
while ($self->{custommode_current}->check_options()) {
162171
$self->{custommode_current} = $self->{custom_modes}->{$self->{custommode_name}}->new(noptions => 1, options => $self->{options}, output => $self->{output}, mode => $self->{custommode_name});
163-
$self->{custommode_current}->set_options(option_results => $self->{option_results});
172+
# Call custom mode set_options if it's defined, otherwise set option_results directly
173+
if ($self->{custommode_current}->can('set_options')) {
174+
$self->{custommode_current}->set_options(option_results => $self->{option_results});
175+
} else {
176+
$self->{custommode_current}->{option_results} = $self->{option_results};
177+
}
164178
push @{$self->{custommode_stored}}, $self->{custommode_current};
165179
}
166180
$self->{mode}->check_options(
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#
2+
# Copyright 2026-Present Centreon (http://www.centreon.com/)
3+
#
4+
# Centreon is a full-fledged industry-strength solution that meets
5+
# the needs in IT infrastructure and application monitoring for
6+
# service performance.
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
#
20+
21+
package cloud::linux::libvirt::local::custom::virshcli;
22+
23+
use strict;
24+
use warnings;
25+
use centreon::plugins::misc;
26+
27+
sub new {
28+
my ($class, %options) = @_;
29+
my $self = {};
30+
bless $self, $class;
31+
32+
unless ($options{output}) {
33+
print "Class Custom: Need to specify 'output' argument.\n";
34+
exit 3;
35+
}
36+
$options{output}->option_exit(short_msg => "Class Custom: Need to specify 'options' argument.")
37+
unless $options{options};
38+
39+
unless ($options{noptions}) {
40+
$options{options}->add_options(arguments => {
41+
'connect-uri:s' => { name => 'connect_uri', default => 'qemu:///system' },
42+
'virsh-path:s' => { name => 'virsh_path', default => '/usr/bin' },
43+
'timeout:s' => { name => 'timeout', default => 30 },
44+
'sudo' => { name => 'sudo' }
45+
});
46+
}
47+
48+
$options{options}->add_help(package => __PACKAGE__, sections => 'VIRSHCLI OPTIONS', once => 1);
49+
50+
$self->{output} = $options{output};
51+
$self->{custommode_name} = $options{custommode_name};
52+
53+
return $self;
54+
}
55+
56+
sub check_options {
57+
my ($self, %options) = @_;
58+
59+
$self->{$_} = $self->{option_results}->{$_}
60+
foreach (qw(connect_uri virsh_path timeout sudo));
61+
62+
return 0;
63+
}
64+
65+
sub get_identifier {
66+
my ($self, %options) = @_;
67+
68+
my $id = $self->{connect_uri} =~ s/[^a-zA-Z0-9_-]/_/gr;
69+
return $id;
70+
}
71+
72+
sub execute_command {
73+
my ($self, %options) = @_;
74+
75+
# $options{virsh_args}: virsh subcommand and its arguments (e.g. 'list --all')
76+
my ($stdout) = centreon::plugins::misc::execute(
77+
output => $self->{output},
78+
sudo => $self->{sudo},
79+
options => { timeout => $self->{timeout} },
80+
command => 'virsh',
81+
command_path => $self->{virsh_path},
82+
command_options => '--connect ' . $self->{connect_uri} . ' ' . $options{virsh_args},
83+
no_quit => $options{no_quit},
84+
no_shell_interpretation => 1
85+
);
86+
87+
$self->{output}->output_add(long_msg => "virsh response: $stdout", debug => 1);
88+
89+
return $stdout;
90+
}
91+
92+
1;
93+
94+
__END__
95+
96+
=head1 NAME
97+
98+
C<virshcli>
99+
100+
=head1 VIRSHCLI OPTIONS
101+
102+
Libvirt C<virsh> CLI custom mode.
103+
104+
=over 8
105+
106+
=item B<--connect-uri>
107+
108+
Libvirt connection URI (default: 'qemu:///system').
109+
Examples: qemu:///system, qemu+ssh://user@host/system, xen:///.
110+
111+
=item B<--virsh-path>
112+
113+
Path to the C<virsh> binary directory (default: '/usr/bin').
114+
115+
=item B<--timeout>
116+
117+
Timeout in seconds for C<virsh> commands (default: 30).
118+
119+
=item B<--sudo>
120+
121+
Run C<virsh> commands with sudo.
122+
123+
=back
124+
125+
=head1 DESCRIPTION
126+
127+
B<custom>.
128+
129+
=cut
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#
2+
# Copyright 2026-Present Centreon (http://www.centreon.com/)
3+
#
4+
# Centreon is a full-fledged industry-strength solution that meets
5+
# the needs in IT infrastructure and application monitoring for
6+
# service performance.
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
#
20+
21+
package cloud::linux::libvirt::local::mode::discovery;
22+
23+
use base qw(centreon::plugins::mode);
24+
25+
use strict;
26+
use warnings;
27+
use centreon::plugins::misc qw(is_excluded json_encode convert_bytes);
28+
29+
sub new {
30+
my ($class, %options) = @_;
31+
my $self = $class->SUPER::new(package => __PACKAGE__, %options);
32+
bless $self, $class;
33+
34+
$options{options}->add_options(arguments => {
35+
'prettify' => { name => 'prettify', default => 0 },
36+
'include-state:s' => { name => 'include_state' },
37+
'exclude-state:s' => { name => 'exclude_state' }
38+
});
39+
40+
return $self;
41+
}
42+
43+
sub check_options {
44+
my ($self, %options) = @_;
45+
$self->SUPER::init(%options);
46+
}
47+
48+
sub run {
49+
my ($self, %options) = @_;
50+
51+
my $disco_stats;
52+
$disco_stats->{start_time} = time();
53+
54+
# virsh list --all
55+
# Id Name State
56+
# -----------------------------------
57+
# 1 myvm running
58+
# - stopped-vm shut off
59+
my $stdout = $options{custom}->execute_command(virsh_args => 'list --all');
60+
61+
my @results;
62+
foreach (split(/\n/, $stdout)) {
63+
next if /^\s*(Id\s+Name)\s*/i;
64+
next unless /^\s*(\S+)\s+(\S+)\s+(.+?)\s*$/;
65+
my ($id, $name, $state) = ($1, $2, $3);
66+
$state = lc($state) =~ s/\s+/_/gr; # Normalize state (e.g., "shut off" -> "shut_off")
67+
68+
next if is_excluded($state, $self->{option_results}->{include_state}, $self->{option_results}->{exclude_state});
69+
70+
my $vm = {
71+
vm_name => $name,
72+
vm_id => $id,
73+
state => $state
74+
};
75+
76+
# Enrich with dominfo for running VMs
77+
if ($state eq 'running') {
78+
my $info = $options{custom}->execute_command(
79+
virsh_args => "dominfo $name",
80+
no_quit => 1
81+
);
82+
if ($info) {
83+
$vm->{vcpus} = $1 if $info =~ /CPU\(s\):\s+(\d+)/i;
84+
$vm->{max_mem_bytes} = convert_bytes(value => $1, unit => $2) if $info =~ /Max memory:\s+(\d+)\s+(.*)/i;
85+
$vm->{uuid} = $1 if $info =~ /UUID:\s+(\S+)/i;
86+
}
87+
}
88+
89+
push @results, $vm;
90+
}
91+
92+
$disco_stats->{end_time} = time();
93+
$disco_stats->{duration} = $disco_stats->{end_time} - $disco_stats->{start_time};
94+
$disco_stats->{discovered_items} = scalar(@results);
95+
$disco_stats->{results} = \@results;
96+
97+
$self->{output}->output_add(
98+
severity => 'OK',
99+
short_msg => json_encode($disco_stats, prettify => $self->{option_results}->{prettify}, output => $self->{output})
100+
);
101+
$self->{output}->display(nolabel => 1, force_ignore_perfdata => 1);
102+
$self->{output}->exit();
103+
}
104+
105+
1;
106+
107+
__END__
108+
109+
=head1 MODE
110+
111+
Discover virtual machines (C<virsh list --all>).
112+
Returns a JSON document suitable for Centreon Auto Discovery.
113+
114+
=over 8
115+
116+
=item B<--prettify>
117+
118+
Format JSON output with indentation.
119+
120+
=item B<--include-state>
121+
122+
Filter VMs by state (regexp).
123+
124+
=item B<--exclude-state>
125+
126+
Exclude VMs whose state matches this regexp.
127+
128+
=back
129+
130+
=cut

0 commit comments

Comments
 (0)