diff --git a/network/cisco/meraki/cloudcontroller/restapi/custom/api.pm b/network/cisco/meraki/cloudcontroller/restapi/custom/api.pm index 66bb8500f..12b6085bf 100644 --- a/network/cisco/meraki/cloudcontroller/restapi/custom/api.pm +++ b/network/cisco/meraki/cloudcontroller/restapi/custom/api.pm @@ -58,6 +58,7 @@ sub new { $self->{http} = centreon::plugins::http->new(%options); $self->{cache} = centreon::plugins::statefile->new(%options); $self->{cache_checked} = 0; + $self->{cache_uplink_loss_latency} = {}; return $self; } @@ -100,6 +101,28 @@ sub get_token { return md5_hex($self->{api_token}); } +sub get_shard_hostname { + my ($self, %options) = @_; + + my $organization_id = $options{organization_id}; + my $network_id = $options{network_id}; + if (defined($options{serial}) && defined($self->{cache_devices}->{ $options{serial} })) { + $network_id = $self->{cache_devices}->{ $options{serial} }->{networkId}; + } + if (defined($network_id) && defined($self->{cache_networks}->{$network_id})) { + $organization_id = $self->{cache_networks}->{$network_id}->{organizationId}; + } + + if (defined($organization_id)) { + if (defined($self->{cache_organizations}->{$organization_id}) + && $self->{cache_organizations}->{$organization_id}->{url} =~ /^(?:http|https):\/\/(.*?)\//) { + return $1; + } + } + + return undef; +} + sub get_cache_organizations { my ($self, %options) = @_; @@ -114,6 +137,13 @@ sub get_cache_networks { return $self->{cache_networks}; } +sub get_organization_id { + my ($self, %options) = @_; + + $self->cache_meraki_entities(); + return $self->{cache_networks}->{ $options{network_id} }->{organizationId}; +} + sub get_cache_devices { my ($self, %options) = @_; @@ -124,7 +154,6 @@ sub get_cache_devices { sub build_options_for_httplib { my ($self, %options) = @_; - $self->{option_results}->{hostname} = $self->{hostname}; $self->{option_results}->{timeout} = $self->{timeout}; $self->{option_results}->{port} = $self->{port}; $self->{option_results}->{proto} = $self->{proto}; @@ -143,26 +172,38 @@ sub request_api { $self->settings(); + my $hostname = $self->{hostname}; + if (defined($options{hostname})) { + $hostname = $options{hostname}; + } + #400: Bad Request- You did something wrong, e.g. a malformed request or missing parameter. #403: Forbidden- You don't have permission to do that. #404: Not found- No such URL, or you don't have access to the API or organization at all. #429: Too Many Requests- You submitted more than 5 calls in 1 second to an Organization, triggering rate limiting. This also applies for API calls made across multiple organizations that triggers rate limiting for one of the organizations. while (1) { my $response = $self->{http}->request( + hostname => $hostname, url_path => '/api/v0' . $options{endpoint}, critical_status => '', warning_status => '', - unknown_status => '(%{http_code} < 200 or %{http_code} >= 300) and %{http_code} != 429' + unknown_status => '' ); my $code = $self->{http}->get_code(); return [] if ($code == 403 && $self->{ignore_permission_errors} == 1); + return undef if (defined($options{ignore_codes}) && defined($options{ignore_codes}->{$code})); if ($code == 429) { sleep(1); continue; } + if ($code < 200 || $code >= 300) { + $self->{output}->add_option_msg(short_msg => $code . ' ' . $self->{http}->get_message()); + $self->{output}->option_exit(); + } + my $content; eval { $content = JSON::XS->new->allow_nonref(1)->decode($response); @@ -232,7 +273,8 @@ sub get_networks { my $results = {}; foreach my $id (keys %{$self->{cache_organizations}}) { my $datas = $self->request_api( - endpoint => '/organizations/' . $id . '/networks' + endpoint => '/organizations/' . $id . '/networks', + hostname => $self->get_shard_hostname(organization_id => $id) ); $results->{$_->{id}} = $_ foreach (@$datas); } @@ -249,7 +291,8 @@ sub get_devices { my $results = {}; foreach my $id (keys %{$self->{cache_organizations}}) { my $datas = $self->request_api( - endpoint => '/organizations/' . $id . '/devices' + endpoint => '/organizations/' . $id . '/devices', + hostname => $self->get_shard_hostname(organization_id => $id) ); $results->{$_->{serial}} = $_ foreach (@$datas); } @@ -269,11 +312,6 @@ sub filter_networks { } } - if (scalar(@$network_ids) > 5) { - $self->{output}->add_option_msg(short_msg => 'cannot check than 5 networks at once (api rate limit)'); - $self->{output}->option_exit(); - } - return $network_ids; } @@ -302,7 +340,11 @@ sub get_networks_connection_stats { $timespan = 1 if ($timespan <= 0); my $results = {}; foreach my $id (@$network_ids) { - my $datas = $self->request_api(endpoint => '/networks/' . $id . '/connectionStats?timespan=' . $options{timespan}); + my $datas = $self->request_api( + endpoint => '/networks/' . $id . '/connectionStats?timespan=' . $options{timespan}, + hostname => $self->get_shard_hostname(network_id => $id), + ignore_codes => { 400 => 1 } + ); $results->{$id} = $datas; } @@ -321,6 +363,8 @@ sub get_networks_clients { foreach my $id (@$network_ids) { my $datas = $self->request_api( endpoint => '/networks/' . $id . '/clients?timespan=' . $options{timespan}, + hostname => $self->get_shard_hostname(network_id => $id), + ignore_codes => { 400 => 1 } ); $results->{$id} = $datas; } @@ -336,7 +380,8 @@ sub get_organization_device_statuses { my $results = {}; foreach my $id (@$organization_ids) { my $datas = $self->request_api( - endpoint => '/organizations/' . $id . '/deviceStatuses' + endpoint => '/organizations/' . $id . '/deviceStatuses', + hostname => $self->get_shard_hostname(organization_id => $id) ); foreach (@$datas) { $results->{$_->{serial}} = $_; @@ -358,7 +403,8 @@ sub get_organization_api_requests_overview { my $results = {}; foreach my $id (@$organization_ids) { $results->{$id} = $self->request_api( - endpoint => '/organizations/' . $id . '/apiRequests/overview?timespan=' . $options{timespan} + endpoint => '/organizations/' . $id . '/apiRequests/overview?timespan=' . $options{timespan}, + hostname => $self->get_shard_hostname(organization_id => $id) ); } @@ -368,68 +414,77 @@ sub get_organization_api_requests_overview { sub get_network_device_connection_stats { my ($self, %options) = @_; - if (scalar(keys %{$options{devices}}) > 5) { - $self->{output}->add_option_msg(short_msg => 'cannot check more than 5 devices at once (api rate limit)'); - $self->{output}->option_exit(); - } - $self->cache_meraki_entities(); my $timespan = defined($options{timespan}) ? $options{timespan} : 300; $timespan = 1 if ($timespan <= 0); - my $results = {}; - foreach (keys %{$options{devices}}) { - my $data = $self->request_api( - endpoint => '/networks/' . $options{devices}->{$_} . '/devices/' . $_ . '/connectionStats?timespan=' . $options{timespan} - ); - $results->{$_} = $data; - } - - return $results; + return $self->request_api( + endpoint => '/networks/' . $options{network_id} . '/devices/' . $options{serial} . '/connectionStats?timespan=' . $options{timespan}, + hostname => $self->get_shard_hostname(network_id => $options{network_id}) + ); } sub get_network_device_uplink { my ($self, %options) = @_; - if (scalar(keys %{$options{devices}}) > 5) { - $self->{output}->add_option_msg(short_msg => 'cannot check more than 5 devices at once (api rate limit)'); - $self->{output}->option_exit(); - } - $self->cache_meraki_entities(); - my $results = {}; - foreach (keys %{$options{devices}}) { - my $data = $self->request_api( - endpoint => '/networks/' . $options{devices}->{$_} . '/devices/' . $_ . '/uplink' - ); - $results->{$_} = $data; - } - - return $results; + return $self->request_api( + endpoint => '/networks/' . $options{network_id} . '/devices/' . $options{serial} . '/uplink', + hostname => $self->get_shard_hostname(network_id => $options{network_id}) + ); } sub get_device_clients { my ($self, %options) = @_; - if (scalar(keys %{$options{devices}}) > 5) { - $self->{output}->add_option_msg(short_msg => 'cannot check more than 5 devices at once (api rate limit)'); - $self->{output}->option_exit(); - } + $self->cache_meraki_entities(); + my $timespan = defined($options{timespan}) ? $options{timespan} : 300; + $timespan = 1 if ($timespan <= 0); + + return $self->request_api( + endpoint => '/devices/' . $options{serial} . '/clients?timespan=' . $options{timespan}, + hostname => $self->get_shard_hostname(serial => $options{serial}) + ); +} + +sub get_network_device_performance { + my ($self, %options) = @_; + + $self->cache_meraki_entities(); + + # 400 = feature not supported. 204 = no content + return $self->request_api( + endpoint => '/networks/' . $options{network_id} . '/devices/' . $options{serial} . '/performance', + hostname => $self->get_shard_hostname(network_id => $options{network_id}), + ignore_codes => { 400 => 1, 204 => 1 } + ); +} + +sub get_organization_uplink_loss_and_latency { + my ($self, %options) = @_; $self->cache_meraki_entities(); my $timespan = defined($options{timespan}) ? $options{timespan} : 300; $timespan = 1 if ($timespan <= 0); - my $results = {}; - foreach (keys %{$options{devices}}) { - my $data = $self->request_api( - endpoint => '/devices/' . $_ . '/clients?timespan=' . $options{timespan} + if (!defined($self->{cache_uplink_loss_latency}->{ $options{organization_id} })) { + $self->{cache_uplink_loss_latency}->{ $options{organization_id} } = $self->request_api( + endpoint => '/organizations/' . $options{organization_id} . '/uplinksLossAndLatency?timespan=' . $options{timespan}, + hostname => $self->get_shard_hostname(organization_id => $options{organization_id}) ); - $results->{$_} = $data; } - return $results; + my $result = {}; + if (defined($self->{cache_uplink_loss_latency}->{ $options{organization_id} })) { + foreach (@{$self->{cache_uplink_loss_latency}->{ $options{organization_id} }}) { + if ($options{serial} eq $_->{serial}) { + $result->{ $_->{uplink} } = $_; + } + } + } + + return $result; } 1; diff --git a/network/cisco/meraki/cloudcontroller/restapi/mode/devices.pm b/network/cisco/meraki/cloudcontroller/restapi/mode/devices.pm index f9d5f8796..c1b2ece11 100644 --- a/network/cisco/meraki/cloudcontroller/restapi/mode/devices.pm +++ b/network/cisco/meraki/cloudcontroller/restapi/mode/devices.pm @@ -24,7 +24,7 @@ use base qw(centreon::plugins::templates::counter); use strict; use warnings; -use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold catalog_status_calc); +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); use Digest::MD5 qw(md5_hex); sub custom_status_output { @@ -47,6 +47,7 @@ sub set_counters { { name => 'devices', type => 3, cb_prefix_output => 'prefix_device_output', cb_long_output => 'device_long_output', indent_long_output => ' ', message_multiple => 'All devices are ok', group => [ { name => 'device_status', type => 0, skipped_code => { -10 => 1 } }, + { name => 'device_performance', type => 0, skipped_code => { -10 => 1 } }, { name => 'device_connections', type => 0, cb_prefix_output => 'prefix_connection_output', skipped_code => { -10 => 1 } }, { name => 'device_traffic', type => 0, cb_prefix_output => 'prefix_traffic_output', skipped_code => { -10 => 1, -11 => 1 } }, { name => 'device_links', display_long => 1, cb_prefix_output => 'prefix_link_output', message_multiple => 'All links are ok', type => 1, skipped_code => { -10 => 1 } }, @@ -82,54 +83,64 @@ sub set_counters { ]; $self->{maps_counters}->{device_status} = [ - { label => 'status', threshold => 0, set => { + { label => 'status', type => 2, critical_default => '%{status} =~ /alerting/i', set => { key_values => [ { name => 'status' }, { name => 'display' } ], - closure_custom_calc => \&catalog_status_calc, closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, - closure_custom_threshold_check => \&catalog_status_threshold + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + } + ]; + + $self->{maps_counters}->{device_performance} = [ + { label => 'load', nlabel => 'device.load.count', set => { + key_values => [ { name => 'perfscore' } ], + output_template => 'load: %s', + perfdatas => [ + { template => '%d', min => 0, max => 100, label_extra_instance => 1 } + ] } } ]; $self->{maps_counters}->{device_connections} = [ { label => 'connections-success', nlabel => 'device.connections.success.count', set => { - key_values => [ { name => 'assoc' }, { name => 'display' } ], + key_values => [ { name => 'assoc' } ], output_template => 'success: %s', perfdatas => [ - { template => '%d', min => 0, label_extra_instance => 1, instance_use => 'display' } + { template => '%d', min => 0, label_extra_instance => 1 } ] } }, { label => 'connections-auth', nlabel => 'device.connections.auth.count', display_ok => 0, set => { - key_values => [ { name => 'auth' }, { name => 'display' } ], + key_values => [ { name => 'auth' } ], output_template => 'auth: %s', perfdatas => [ - { template => '%d', min => 0, label_extra_instance => 1, instance_use => 'display' } + { template => '%d', min => 0, label_extra_instance => 1 } ] } }, { label => 'connections-assoc', nlabel => 'device.connections.assoc.count', display_ok => 0, set => { - key_values => [ { name => 'assoc' }, { name => 'display' } ], + key_values => [ { name => 'assoc' } ], output_template => 'assoc: %s', perfdatas => [ - { template => '%d', min => 0, label_extra_instance => 1, instance_use => 'display' } + { template => '%d', min => 0, label_extra_instance => 1 } ] } }, { label => 'connections-dhcp', nlabel => 'device.connections.dhcp.count', display_ok => 0, set => { - key_values => [ { name => 'dhcp' }, { name => 'display' } ], + key_values => [ { name => 'dhcp' } ], output_template => 'dhcp: %s', perfdatas => [ - { template => '%d', min => 0, label_extra_instance => 1, instance_use => 'display' } + { template => '%d', min => 0, label_extra_instance => 1 } ] } }, { label => 'connections-dns', nlabel => 'device.connections.dns.count', display_ok => 0, set => { - key_values => [ { name => 'dns' }, { name => 'display' } ], + key_values => [ { name => 'dns' } ], output_template => 'dns: %s', perfdatas => [ - { template => '%d', min => 0, label_extra_instance => 1, instance_use => 'display' } + { template => '%d', min => 0, label_extra_instance => 1 } ] } } @@ -157,12 +168,27 @@ sub set_counters { ]; $self->{maps_counters}->{device_links} = [ - { label => 'link-status', threshold => 0, set => { + { label => 'link-status', type => 2, critical_default => '%{link_status} =~ /failed/i', set => { key_values => [ { name => 'link_status' }, { name => 'display' } ], - closure_custom_calc => \&catalog_status_calc, closure_custom_output => $self->can('custom_link_status_output'), closure_custom_perfdata => sub { return 0; }, - closure_custom_threshold_check => \&catalog_status_threshold + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { label => 'link-latency', nlabel => 'device.link.latency.milliseconds', set => { + key_values => [ { name => 'latency_ms' } ], + output_template => 'latency: %.2f ms', + perfdatas => [ + { template => '%.2f', min => 0, unit => 'ms', label_extra_instance => 1 } + ] + } + }, + { label => 'link-loss', nlabel => 'device.link.loss.percentage', set => { + key_values => [ { name => 'loss_percent' } ], + output_template => 'loss: %.2f %%', + perfdatas => [ + { template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1 } + ] } } ]; @@ -210,26 +236,119 @@ sub new { bless $self, $class; $options{options}->add_options(arguments => { - 'filter-device-name:s' => { name => 'filter_device_name' }, - 'unknown-status:s' => { name => 'unknown_status', default => '' }, - 'warning-status:s' => { name => 'warning_status', default => '' }, - 'critical-status:s' => { name => 'critical_status', default => '%{status} =~ /alerting/i' }, - 'unknown-link-status:s' => { name => 'unknown_link_status', default => '' }, - 'warning-link-status:s' => { name => 'warning_link_status', default => '' }, - 'critical-link-status:s' => { name => 'critical_link_status', default => '%{link_status} =~ /failed/i' } + 'filter-device-name:s' => { name => 'filter_device_name' }, + 'filter-network-id:s' => { name => 'filter_network_id' } }); return $self; } -sub check_options { +sub add_connection_stats { my ($self, %options) = @_; - $self->SUPER::check_options(%options); - $self->change_macros(macros => [ - 'unknown_status', 'warning_status', 'critical_status', - 'unknown_link_status', 'warning_link_status', 'critical_link_status' - ]); + my $connections = $options{custom}->get_network_device_connection_stats( + timespan => $options{timespan}, + serial => $options{serial}, + network_id => $options{network_id} + ); + + $self->{devices}->{ $options{serial} }->{device_connections} = { + assoc => defined($connections->{assoc}) ? $connections->{assoc} : 0, + auth => defined($connections->{auth}) ? $connections->{auth} : 0, + dhcp => defined($connections->{dhcp}) ? $connections->{dhcp} : 0, + dns => defined($connections->{dns}) ? $connections->{dns} : 0, + success => defined($connections->{assoc}) ? $connections->{success} : 0 + }; +} + +sub add_clients { + my ($self, %options) = @_; + + my $clients = $options{custom}->get_device_clients( + timespan => $options{timespan}, + serial => $options{serial} + ); + + $self->{devices}->{ $options{serial} }->{device_traffic} = { + display => $options{name}, + traffic_in => 0, + traffic_out => 0 + }; + + if (defined($clients)) { + foreach (@$clients) { + $self->{devices}->{ $options{serial} }->{device_traffic}->{traffic_in} += $_->{usage}->{recv} * 8; + $self->{devices}->{ $options{serial} }->{device_traffic}->{traffic_out} += $_->{usage}->{sent} * 8; + } + } +} + +sub add_uplink { + my ($self, %options) = @_; + + my $links = $options{custom}->get_network_device_uplink( + serial => $options{serial}, + network_id => $options{network_id} + ); + + if (defined($links)) { + foreach (@$links) { + my $interface = lc($_->{interface}); + $interface =~ s/\s+//g; + $self->{devices}->{ $options{serial} }->{device_links}->{$interface} = { + display => $interface, + link_status => lc($_->{status}) + }; + } + } +} + +sub add_uplink_loss_latency { + my ($self, %options) = @_; + + # 5 minutes max timespan + my $links = $options{custom}->get_organization_uplink_loss_and_latency( + timespan => 300, + serial => $options{serial}, + organization_id => $options{custom}->get_organization_id(network_id => $options{network_id}) + ); + + return if (!defined($links)); + + foreach (values %$links) { + my $interface = lc($_->{uplink}); + $interface =~ s/\s+//g; + next if (!defined($self->{devices}->{ $options{serial} }->{device_links}->{$interface})); + + my ($latency, $loss) = (0, 0); + foreach my $ts (@{$_->{timeSeries}}) { + $latency += $ts->{latencyMs}; + $loss += $ts->{lossPercent}; + } + + if (scalar(@{$_->{timeSeries}}) > 0) { + $latency /= scalar(@{$_->{timeSeries}}); + $loss /= scalar(@{$_->{timeSeries}}); + } + + $self->{devices}->{ $options{serial} }->{device_links}->{$interface}->{loss_percent} = $loss; + $self->{devices}->{ $options{serial} }->{device_links}->{$interface}->{latency_ms} = $latency; + } +} + +sub add_performance { + my ($self, %options) = @_; + + my $perf = $options{custom}->get_network_device_performance( + serial => $options{serial}, + network_id => $options{network_id} + ); + + if (defined($perf) && defined($perf->{perfScore})) { + $self->{devices}->{ $options{serial} }->{device_performance} = { + perfscore => $perf->{perfScore} + }; + } } sub manage_selection { @@ -250,14 +369,24 @@ sub manage_selection { $self->{output}->output_add(long_msg => "skipping device '" . $_->{name} . "': no matching filter.", debug => 1); next; } + if (defined($self->{option_results}->{filter_network_id}) && $self->{option_results}->{filter_network_id} ne '' && + $_->{networkId} !~ /$self->{option_results}->{filter_network_id}/) { + $self->{output}->output_add(long_msg => "skipping device '" . $_->{name} . "': no matching filter.", debug => 1); + next; + } - $devices->{$_->{serial}} = $_->{networkId}; + $devices->{ $_->{serial} } = $_->{model}; } my $device_statuses = $options{custom}->get_organization_device_statuses(); - my $connections = $options{custom}->get_network_device_connection_stats(timespan => $timespan, devices => $devices); - my $clients = $options{custom}->get_device_clients(timespan => $timespan, devices => $devices); - my $links = $options{custom}->get_network_device_uplink(devices => $devices); + + # | /clients | /connectionStats | /performance | /uplink | /uplinksLossAndLatency + #-------------------|----------|---------------------------------|---------|------------------------- + # MV [camera] | | | | X | + # MS [switch] | X | | | X | + # MG [cellullar gw] | X | X | | X | + # MX [appliance] | X | | X | X | X + # MR [wireless] | X | X | | X | $self->{global} = { total => 0, online => 0, offline => 0, alerting => 0 }; $self->{devices} = {}; @@ -268,36 +397,47 @@ sub manage_selection { display => $cache_devices->{$serial}->{name}, status => $device_statuses->{$serial}->{status} }, - device_connections => { - display => $cache_devices->{$serial}->{name}, - assoc => defined($connections->{$serial}->{assoc}) ? $connections->{$serial}->{assoc} : 0, - auth => defined($connections->{$serial}->{auth}) ? $connections->{$serial}->{auth} : 0, - dhcp => defined($connections->{$serial}->{dhcp}) ? $connections->{$serial}->{dhcp} : 0, - dns => defined($connections->{$serial}->{dns}) ? $connections->{$serial}->{dns} : 0, - success => defined($connections->{$serial}->{assoc}) ? $connections->{$serial}->{success} : 0, - }, - device_traffic => { - display => $cache_devices->{$serial}->{name}, - traffic_in => 0, - traffic_out => 0 - }, device_links => {} }; - if (defined($clients->{$serial})) { - foreach (@{$clients->{$serial}}) { - $self->{devices}->{$serial}->{device_traffic}->{traffic_in} += $_->{usage}->{recv} * 8; - $self->{devices}->{$serial}->{device_traffic}->{traffic_out} += $_->{usage}->{sent} * 8; - } + if ($devices->{$serial} =~ /^(?:MG|MR)/) { + $self->add_connection_stats( + custom => $options{custom}, + timespan => $timespan, + serial => $serial, + name => $cache_devices->{$serial}->{name}, + network_id => $cache_devices->{$serial}->{networkId} + ); } - - if (defined($links->{$serial})) { - foreach (@{$links->{$serial}}) { - $self->{devices}->{$serial}->{device_links}->{$_->{interface}} = { - display => $_->{interface}, - link_status => lc($_->{status}) - }; - } + if ($devices->{$serial} =~ /^(?:MS|MG|MR|MX)/) { + $self->add_clients( + custom => $options{custom}, + timespan => $timespan, + serial => $serial, + name => $cache_devices->{$serial}->{name} + ); + } + if ($devices->{$serial} =~ /^(?:MV|MS|MG|MR|MX)/) { + $self->add_uplink( + custom => $options{custom}, + serial => $serial, + name => $cache_devices->{$serial}->{name}, + network_id => $cache_devices->{$serial}->{networkId} + ); + } + if ($devices->{$serial} =~ /^MX/) { + $self->add_performance( + custom => $options{custom}, + serial => $serial, + name => $cache_devices->{$serial}->{name}, + network_id => $cache_devices->{$serial}->{networkId} + ); + $self->add_uplink_loss_latency( + custom => $options{custom}, + timespan => $timespan, + serial => $serial, + network_id => $cache_devices->{$serial}->{networkId} + ); } $self->{global}->{total}++; @@ -324,6 +464,10 @@ Check devices. Filter device name (Can be a regexp). +=item B<--filter-network-id> + +Filter network id (Can be a regexp). + =item B<--unknown-status> Set unknown threshold for status.