From aea4161b609de7ae1f1e0c9d35de1eb258ea6a05 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 26 Feb 2021 11:30:49 +0100 Subject: [PATCH] enh(cloud/kubernetes): add nodes discovery, implement API pagination (#2618) --- .../cloud/kubernetes/custom/api.pm | 52 ++++++-- .../cloud/kubernetes/mode/daemonsetstatus.pm | 2 +- .../cloud/kubernetes/mode/deploymentstatus.pm | 2 +- .../cloud/kubernetes/mode/listdaemonsets.pm | 2 +- .../cloud/kubernetes/mode/listdeployments.pm | 2 +- .../cloud/kubernetes/mode/listingresses.pm | 2 +- .../cloud/kubernetes/mode/listnamespaces.pm | 2 +- .../cloud/kubernetes/mode/listnodes.pm | 2 +- .../cloud/kubernetes/mode/listpods.pm | 2 +- .../cloud/kubernetes/mode/listreplicasets.pm | 2 +- .../cloud/kubernetes/mode/listservices.pm | 2 +- .../cloud/kubernetes/mode/liststatefulsets.pm | 2 +- .../cloud/kubernetes/mode/nodesdiscovery.pm | 116 ++++++++++++++++++ .../cloud/kubernetes/mode/nodeusage.pm | 4 +- .../cloud/kubernetes/mode/podstatus.pm | 2 +- centreon-plugins/cloud/kubernetes/plugin.pm | 1 + 16 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 centreon-plugins/cloud/kubernetes/mode/nodesdiscovery.pm diff --git a/centreon-plugins/cloud/kubernetes/custom/api.pm b/centreon-plugins/cloud/kubernetes/custom/api.pm index 5c35c763e..7837c5761 100644 --- a/centreon-plugins/cloud/kubernetes/custom/api.pm +++ b/centreon-plugins/cloud/kubernetes/custom/api.pm @@ -48,6 +48,7 @@ sub new { 'proto:s' => { name => 'proto' }, 'token:s' => { name => 'token' }, 'timeout:s' => { name => 'timeout' }, + 'limit:s' => { name => 'limit' }, 'config-file:s' => { name => 'config_file' } }); } @@ -75,6 +76,7 @@ sub check_options { $self->{proto} = (defined($self->{option_results}->{proto})) ? $self->{option_results}->{proto} : 'https'; $self->{timeout} = (defined($self->{option_results}->{timeout})) ? $self->{option_results}->{timeout} : 10; $self->{token} = (defined($self->{option_results}->{token})) ? $self->{option_results}->{token} : ''; + $self->{limit} = (defined($self->{option_results}->{limit})) ? $self->{option_results}->{limit} : '100'; if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify --hostname option."); @@ -119,7 +121,7 @@ sub request_api { $self->{output}->output_add(long_msg => "URL: '" . $self->{proto} . '://' . $self->{hostname} . ':' . $self->{port} . $options{url_path} . "'", debug => 1); - my $response = $self->{http}->request(url_path => $options{url_path}); + my $response = $self->{http}->request(%options); if ($self->{http}->get_code() != 200) { my $decoded; @@ -155,10 +157,34 @@ sub request_api { return $decoded; } +sub request_api_paginate { + my ($self, %options) = @_; + + my @items; + my @get_param = ( 'limit=' . $self->{limit} ); + push @get_param, @{$options{get_param}} if (defined($options{get_param})); + + while (1) { + my $response = $self->request_api( + method => $options{method}, + url_path => $options{url_path}, + get_param => \@get_param + ); + last if (!defined($response->{items})); + push @items, @{$response->{items}}; + + last if (!defined($response->{metadata}->{continue})); + @get_param = ( 'limit=' . $self->{limit}, 'continue=' . $response->{metadata}->{continue} ); + push @get_param, @{$options{get_param}} if (defined($options{get_param})); + } + + return \@items; +} + sub kubernetes_list_daemonsets { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/apis/apps/v1/daemonsets'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/apis/apps/v1/daemonsets'); return $response; } @@ -166,7 +192,7 @@ sub kubernetes_list_daemonsets { sub kubernetes_list_deployments { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/apis/apps/v1/deployments'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/apis/apps/v1/deployments'); return $response; } @@ -174,7 +200,7 @@ sub kubernetes_list_deployments { sub kubernetes_list_ingresses { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/apis/extensions/v1beta1/ingresses'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/apis/extensions/v1beta1/ingresses'); return $response; } @@ -182,7 +208,7 @@ sub kubernetes_list_ingresses { sub kubernetes_list_namespaces { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/api/v1/namespaces'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/api/v1/namespaces'); return $response; } @@ -190,7 +216,7 @@ sub kubernetes_list_namespaces { sub kubernetes_list_nodes { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/api/v1/nodes'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/api/v1/nodes'); return $response; } @@ -198,7 +224,7 @@ sub kubernetes_list_nodes { sub kubernetes_list_replicasets { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/apis/apps/v1/replicasets'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/apis/apps/v1/replicasets'); return $response; } @@ -206,7 +232,7 @@ sub kubernetes_list_replicasets { sub kubernetes_list_services { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/apis/v1/services'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/apis/v1/services'); return $response; } @@ -214,7 +240,7 @@ sub kubernetes_list_services { sub kubernetes_list_statefulsets { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/apis/apps/v1/statefulsets'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/apis/apps/v1/statefulsets'); return $response; } @@ -222,7 +248,7 @@ sub kubernetes_list_statefulsets { sub kubernetes_list_pods { my ($self, %options) = @_; - my $response = $self->request_api(method => 'GET', url_path => '/api/v1/pods'); + my $response = $self->request_api_paginate(method => 'GET', url_path => '/api/v1/pods'); return $response; } @@ -261,6 +287,12 @@ Specify https if needed (Default: 'https') Set HTTP timeout +=item B<--limit> + +Number of responses to return for each list calls. + +See https://kubernetes.io/docs/reference/kubernetes-api/common-parameters/common-parameters/#limit + =back =head1 DESCRIPTION diff --git a/centreon-plugins/cloud/kubernetes/mode/daemonsetstatus.pm b/centreon-plugins/cloud/kubernetes/mode/daemonsetstatus.pm index 6138a7d29..5a8fdb36e 100644 --- a/centreon-plugins/cloud/kubernetes/mode/daemonsetstatus.pm +++ b/centreon-plugins/cloud/kubernetes/mode/daemonsetstatus.pm @@ -149,7 +149,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_daemonsets(); - foreach my $daemonset (@{$results->{items}}) { + foreach my $daemonset (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $daemonset->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $daemonset->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/deploymentstatus.pm b/centreon-plugins/cloud/kubernetes/mode/deploymentstatus.pm index f5d43e40d..ddcef9cdc 100644 --- a/centreon-plugins/cloud/kubernetes/mode/deploymentstatus.pm +++ b/centreon-plugins/cloud/kubernetes/mode/deploymentstatus.pm @@ -141,7 +141,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_deployments(); - foreach my $deployment (@{$results->{items}}) { + foreach my $deployment (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $deployment->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $deployment->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listdaemonsets.pm b/centreon-plugins/cloud/kubernetes/mode/listdaemonsets.pm index dedc45fc0..76f26e7cb 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listdaemonsets.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listdaemonsets.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_daemonsets(); - foreach my $daemonset (@{$results->{items}}) { + foreach my $daemonset (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $daemonset->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $daemonset->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listdeployments.pm b/centreon-plugins/cloud/kubernetes/mode/listdeployments.pm index 48692e1d7..a71776e14 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listdeployments.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listdeployments.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_deployments(); - foreach my $deployment (@{$results->{items}}) { + foreach my $deployment (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $deployment->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $deployment->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listingresses.pm b/centreon-plugins/cloud/kubernetes/mode/listingresses.pm index fdd2dfbe2..1f8904206 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listingresses.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listingresses.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_ingresses(); - foreach my $ingress (@{$results->{items}}) { + foreach my $ingress (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $ingress->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $ingress->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listnamespaces.pm b/centreon-plugins/cloud/kubernetes/mode/listnamespaces.pm index 30802c322..572b4c93d 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listnamespaces.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listnamespaces.pm @@ -47,7 +47,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_namespaces(); - foreach my $namespace (@{$results->{items}}) { + foreach my $namespace (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $namespace->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $namespace->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listnodes.pm b/centreon-plugins/cloud/kubernetes/mode/listnodes.pm index 984d4ced6..638b71570 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listnodes.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listnodes.pm @@ -47,7 +47,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_nodes(); - foreach my $node (@{$results->{items}}) { + foreach my $node (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $node->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $node->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listpods.pm b/centreon-plugins/cloud/kubernetes/mode/listpods.pm index 10f857fa8..dc32d282d 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listpods.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listpods.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_pods(); - foreach my $pod (@{$results->{items}}) { + foreach my $pod (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $pod->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $pod->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listreplicasets.pm b/centreon-plugins/cloud/kubernetes/mode/listreplicasets.pm index 63075ba99..3a4db19ae 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listreplicasets.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listreplicasets.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_replicasets(); - foreach my $replicaset (@{$results->{items}}) { + foreach my $replicaset (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $replicaset->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $replicaset->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/listservices.pm b/centreon-plugins/cloud/kubernetes/mode/listservices.pm index d1de6d8d2..dc1c98716 100644 --- a/centreon-plugins/cloud/kubernetes/mode/listservices.pm +++ b/centreon-plugins/cloud/kubernetes/mode/listservices.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_services(); - foreach my $service (@{$results->{items}}) { + foreach my $service (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $service->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $service->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/liststatefulsets.pm b/centreon-plugins/cloud/kubernetes/mode/liststatefulsets.pm index 4e169ba33..767e033da 100644 --- a/centreon-plugins/cloud/kubernetes/mode/liststatefulsets.pm +++ b/centreon-plugins/cloud/kubernetes/mode/liststatefulsets.pm @@ -48,7 +48,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_statefulsets(); - foreach my $statefulset (@{$results->{items}}) { + foreach my $statefulset (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $statefulset->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $statefulset->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/mode/nodesdiscovery.pm b/centreon-plugins/cloud/kubernetes/mode/nodesdiscovery.pm new file mode 100644 index 000000000..d561e4e1d --- /dev/null +++ b/centreon-plugins/cloud/kubernetes/mode/nodesdiscovery.pm @@ -0,0 +1,116 @@ +# +# 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 cloud::kubernetes::mode::nodesdiscovery; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $options{options}->add_options(arguments => { + "prettify" => { name => 'prettify' }, + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +sub run { + my ($self, %options) = @_; + + my @disco_data; + my $disco_stats; + + $disco_stats->{start_time} = time(); + + my $nodes = $options{custom}->kubernetes_list_nodes(); + + foreach my $node (@{$nodes}) { + my %node; + $node{name} = $node->{metadata}->{name}; + $node{uid} = $node->{metadata}->{uid}; + $node{os_image} = $node->{status}->{nodeInfo}->{osImage}; + $node{kubelet_version} = $node->{status}->{nodeInfo}->{kubeletVersion}; + if (defined($node->{metadata}->{labels}->{'node-role.kubernetes.io/control-plane'})) { + $node{node_role} = "control-plane"; + } elsif (defined($node->{metadata}->{labels}->{'node-role.kubernetes.io/master'})) { + $node{node_role} = "master"; + } else { + $node{node_role} = "worker"; + } + + foreach my $address (@{$node->{status}->{addresses}}) { + $node{internal_ip} = $address->{address} if ($address->{type} eq "InternalIP"); + $node{external_ip} = $address->{address} if ($address->{type} eq "ExternalIP"); + $node{hostname} = $address->{address} if ($address->{type} eq "Hostname"); + } + + push @disco_data, \%node; + } + + $disco_stats->{end_time} = time(); + $disco_stats->{duration} = $disco_stats->{end_time} - $disco_stats->{start_time}; + $disco_stats->{discovered_items} = @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 + +Nodes discovery. + +=over 8 + +=item B<--prettify> + +Prettify JSON output. + +=back + +=cut diff --git a/centreon-plugins/cloud/kubernetes/mode/nodeusage.pm b/centreon-plugins/cloud/kubernetes/mode/nodeusage.pm index a0d4eb44c..685ab96df 100644 --- a/centreon-plugins/cloud/kubernetes/mode/nodeusage.pm +++ b/centreon-plugins/cloud/kubernetes/mode/nodeusage.pm @@ -141,7 +141,7 @@ sub manage_selection { my $nodes = $options{custom}->kubernetes_list_nodes(); - foreach my $node (@{$nodes->{items}}) { + foreach my $node (@{$nodes}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $node->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $node->{metadata}->{name} . "': no matching filter name.", debug => 1); @@ -162,7 +162,7 @@ sub manage_selection { my $pods = $options{custom}->kubernetes_list_pods(); - foreach my $pod (@{$pods->{items}}) { + foreach my $pod (@{$pods}) { next if (defined($pod->{spec}->{nodeName}) && !defined($self->{nodes}->{$pod->{spec}->{nodeName}})); $self->{nodes}->{$pod->{spec}->{nodeName}}->{allocated}++; } diff --git a/centreon-plugins/cloud/kubernetes/mode/podstatus.pm b/centreon-plugins/cloud/kubernetes/mode/podstatus.pm index ee2758cf8..ce539e9e0 100644 --- a/centreon-plugins/cloud/kubernetes/mode/podstatus.pm +++ b/centreon-plugins/cloud/kubernetes/mode/podstatus.pm @@ -240,7 +240,7 @@ sub manage_selection { my $results = $options{custom}->kubernetes_list_pods(); - foreach my $pod (@{$results->{items}}) { + foreach my $pod (@{$results}) { if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $pod->{metadata}->{name} !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $pod->{metadata}->{name} . "': no matching filter name.", debug => 1); diff --git a/centreon-plugins/cloud/kubernetes/plugin.pm b/centreon-plugins/cloud/kubernetes/plugin.pm index 52d0857b2..08faf2e01 100644 --- a/centreon-plugins/cloud/kubernetes/plugin.pm +++ b/centreon-plugins/cloud/kubernetes/plugin.pm @@ -42,6 +42,7 @@ sub new { 'list-replicasets' => 'cloud::kubernetes::mode::listreplicasets', 'list-services' => 'cloud::kubernetes::mode::listservices', 'list-statefulsets' => 'cloud::kubernetes::mode::liststatefulsets', + 'nodes-discovery' => 'cloud::kubernetes::mode::nodesdiscovery', 'node-usage' => 'cloud::kubernetes::mode::nodeusage', 'pod-status' => 'cloud::kubernetes::mode::podstatus', );